-
Notifications
You must be signed in to change notification settings - Fork 16
feat: Orchestration image support #294
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
Changes from 43 commits
77b6637
026771b
09816d2
54a2c19
98ba774
75afed1
38f3fd4
f8ea4d0
cd20ca8
a2fe90f
ec899a4
f173057
c57bd1d
0524fd7
60e8120
3754d9c
8ab1514
e99d7b9
7c05d89
5a72a33
6100502
85d077f
7113283
60b97b0
fa1add0
682794a
b671bc7
3bd521c
a152c2f
0f928a8
6ee7290
57330d4
65d01c6
8a0c79d
13c0387
f95c4d3
eaf2632
be70506
fb0a89f
8357b89
8d32086
dff4fb8
b0e6701
29ceee1
d8fd060
bcf0d99
162cb6b
adf81da
a099ee0
4899f36
837fd66
3dd3085
d91ca3b
e438330
a87fa5d
27a1033
b505272
72f66b5
dc69ad1
3d8315e
cd08312
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package com.sap.ai.sdk.orchestration; | ||
|
|
||
| /** Represents an item in a {@link MessageContent} object. */ | ||
| public sealed interface ContentItem permits TextItem, ImageItem {} | ||
Jonas-Isr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package com.sap.ai.sdk.orchestration; | ||
|
|
||
| import java.util.Locale; | ||
| import javax.annotation.Nonnull; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| /** | ||
| * Represents an image item in a {@link MessageContent} object. | ||
| * | ||
| * @param imageUrl the URL of the image | ||
| * @param detailLevel the detail level of the image (optional | ||
| */ | ||
| public record ImageItem(@Nonnull String imageUrl, @Nonnull DetailLevel detailLevel) | ||
| implements ContentItem { | ||
|
|
||
| /** | ||
| * Creates a new image item with the given image URL and detail level. | ||
| * | ||
| * @param imageUrl the URL of the image | ||
| * @param detailLevel the detail level of the image | ||
| */ | ||
| public ImageItem(@Nonnull final String imageUrl, @Nullable final DetailLevel detailLevel) { | ||
| this.imageUrl = imageUrl; | ||
| this.detailLevel = detailLevel != null ? detailLevel : DetailLevel.auto; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new image item with the given image URL. | ||
| * | ||
| * @param imageUrl the URL of the image | ||
| */ | ||
| public ImageItem(@Nonnull final String imageUrl) { | ||
| this(imageUrl, DetailLevel.auto); | ||
| } | ||
Jonas-Isr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /** The detail level of the image. */ | ||
| public enum DetailLevel { | ||
| /** Low detail level. */ | ||
| low, | ||
Jonas-Isr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /** High detail level. */ | ||
| high, | ||
| /** Automatic detail level. */ | ||
| auto; | ||
|
|
||
| /** | ||
| * Converts a string to a detail level. | ||
| * | ||
| * @param str the string to convert | ||
| * @return the detail level | ||
| */ | ||
| @Nonnull | ||
| static DetailLevel fromString(@Nonnull final String str) { | ||
| return DetailLevel.valueOf(str.toLowerCase(Locale.ENGLISH)); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,43 +2,74 @@ | |
|
|
||
| import com.google.common.annotations.Beta; | ||
| import com.sap.ai.sdk.orchestration.model.ChatMessage; | ||
| 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.SingleChatMessage; | ||
| import com.sap.ai.sdk.orchestration.model.TextContent; | ||
| 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 { | ||
|
|
||
| /** | ||
| * A convenience method to create a user message from one or more strings. | ||
| * | ||
| * @param message the message content. | ||
| * @param additionalMessages the additional messages. | ||
| * @return the user message. | ||
| */ | ||
| @Nonnull | ||
| static UserMessage user( | ||
MatKuhr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| @Nonnull final String message, @Nullable final String... additionalMessages) { | ||
| return new UserMessage(message, additionalMessages); | ||
| } | ||
|
|
||
| /** | ||
| * A convenience method to create a user message. | ||
| * | ||
| * @param msg the message content. | ||
| * @param message the message content. | ||
| * @return the user message. | ||
| */ | ||
| @Nonnull | ||
| static UserMessage user(@Nonnull final String msg) { | ||
| return new UserMessage(msg); | ||
| static UserMessage user(@Nonnull final MessageContent message) { | ||
| return new UserMessage(message); | ||
| } | ||
|
|
||
| /** | ||
| * A convenience method to create an assistant message. | ||
| * | ||
| * @param msg the message content. | ||
| * @param message the message content. | ||
| * @return the assistant message. | ||
| */ | ||
| @Nonnull | ||
| static AssistantMessage assistant(@Nonnull final String msg) { | ||
| return new AssistantMessage(msg); | ||
| static AssistantMessage assistant(@Nonnull final String message) { | ||
| return new AssistantMessage(message); | ||
| } | ||
|
|
||
| /** | ||
| * A convenience method to create a system message from one or more strings. | ||
| * | ||
| * @param message the message content. | ||
| * @return the system message. | ||
| */ | ||
| @Nonnull | ||
| static SystemMessage system( | ||
| @Nonnull final String message, @Nullable final String... additionalMessages) { | ||
Jonas-Isr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return new SystemMessage(message, additionalMessages); | ||
| } | ||
|
|
||
| /** | ||
| * A convenience method to create a system message. | ||
| * A convenience method to create a system message. As of now, only text content is supported for | ||
| * system messages by most AIs. | ||
| * | ||
| * @param msg the message content. | ||
| * @param message the message content. | ||
| * @return the system message. | ||
| */ | ||
| @Nonnull | ||
| static SystemMessage system(@Nonnull final String msg) { | ||
| return new SystemMessage(msg); | ||
| static SystemMessage system(@Nonnull final MessageContent message) { | ||
| return new SystemMessage(message); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -48,7 +79,31 @@ static SystemMessage system(@Nonnull final String msg) { | |
| */ | ||
| @Nonnull | ||
| default ChatMessage createChatMessage() { | ||
| return SingleChatMessage.create().role(role()).content(content()); | ||
| final var itemList = this.content().contentItemList(); | ||
| if (itemList.size() == 1 && itemList.get(0) instanceof TextItem textItem) { | ||
| return SingleChatMessage.create().role(role()).content(textItem.text()); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we cannot discern between
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Users can use the constructors instead should this ever matter, right?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The constructor is protected, but the |
||
| } else { | ||
| return MultiChatMessage.create() | ||
| .role(role()) | ||
| .content( | ||
| itemList.stream() | ||
| .map( | ||
| contentItem -> { | ||
| if (contentItem instanceof ImageItem imageItem) { | ||
| return ImageContent.create() | ||
| .type(ImageContent.TypeEnum.IMAGE_URL) | ||
| .imageUrl( | ||
| ImageContentImageUrl.create() | ||
| .url(imageItem.imageUrl()) | ||
| .detail(imageItem.detailLevel().toString())); | ||
| } else { | ||
| return TextContent.create() | ||
| .type(TextContent.TypeEnum.TEXT) | ||
| .text(((TextItem) contentItem).text()); | ||
| } | ||
| }) | ||
| .toList()); | ||
| } | ||
Jonas-Isr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -66,5 +121,5 @@ default ChatMessage createChatMessage() { | |
| */ | ||
| @Nonnull | ||
| @Beta | ||
| String content(); | ||
| MessageContent content(); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a breaking change. (Note that the corresponding methods in We discussed in the meetings that we do not want to offer a convenience method that returns a string representation of messages anymore. Users can/will use
Jonas-Isr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| package com.sap.ai.sdk.orchestration; | ||
|
|
||
| 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.Arrays; | ||
| import java.util.List; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Stream; | ||
| import javax.annotation.Nonnull; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| /** | ||
| * Represents the content of a chat message. | ||
| * | ||
| * @param contentItemList a list of the content items | ||
| */ | ||
| public record MessageContent(@Nonnull List<ContentItem> contentItemList) { | ||
|
||
|
|
||
| MessageContent(@Nonnull final String singleMessage) { | ||
Jonas-Isr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| this(List.of(new TextItem(singleMessage))); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new message content containing one {@link ImageItem} with the given image URL and | ||
| * detail level. | ||
| * | ||
| * @param imageUrl the URL of the image | ||
| * @param detailLevel the detail level of the image | ||
| * @return the new message content | ||
| */ | ||
| @Nonnull | ||
| public static MessageContent image( | ||
| @Nonnull final String imageUrl, @Nullable final ImageItem.DetailLevel detailLevel) { | ||
| return new MessageContent(List.of(new ImageItem(imageUrl, detailLevel))); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new message content containing one {@link ImageItem} with the given image URL. | ||
| * | ||
| * @param imageUrl the URL of the image | ||
| * @return the new message content | ||
| */ | ||
| @Nonnull | ||
| public static MessageContent image(@Nonnull final String imageUrl) { | ||
| return new MessageContent(List.of(new ImageItem(imageUrl))); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new message content containing one or more {@link TextItem}s with the given texts. | ||
| * | ||
| * @param firstInput the first text of the message | ||
| * @param moreInputs additional texts of the message | ||
| * @return the new message content | ||
| */ | ||
| @Nonnull | ||
| public static MessageContent text( | ||
| @Nonnull final String firstInput, @Nullable final String... moreInputs) { | ||
| return new MessageContent( | ||
| Stream.concat( | ||
| Stream.of(firstInput), | ||
| moreInputs == null ? Stream.empty() : Arrays.stream(moreInputs)) | ||
| .map(text -> (ContentItem) new TextItem(text)) | ||
| .toList()); | ||
Jonas-Isr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
| * Creates a new message content containing multiple {@link ContentItem}s. | ||
| * | ||
| * @param multiChatContents the content items | ||
| */ | ||
| public MessageContent(@Nonnull final MultiChatMessageContent... multiChatContents) { | ||
Jonas-Isr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| this(convertIntoMultiMessageList(multiChatContents)); | ||
| } | ||
|
|
||
| @Nonnull | ||
| private static List<ContentItem> convertIntoMultiMessageList( | ||
| @Nonnull final MultiChatMessageContent... multiChatContents) { | ||
| final Function<MultiChatMessageContent, ContentItem> convertMultiChatContent = | ||
| content -> { | ||
| if (content instanceof TextContent text) { | ||
| return new TextItem(text.getText()); | ||
| } else { | ||
| final var imageUrl = ((ImageContent) content).getImageUrl(); | ||
| return new ImageItem( | ||
| imageUrl.getUrl(), ImageItem.DetailLevel.fromString(imageUrl.getDetail())); | ||
| } | ||
| }; | ||
| return Arrays.stream(multiChatContents).map(convertMultiChatContent).toList(); | ||
| } | ||
Jonas-Isr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.