Skip to content

Commit c1746af

Browse files
authored
Merge branch 'main' into spec-update/grounding/main
2 parents 39fad43 + 832fc6c commit c1746af

File tree

53 files changed

+2944
-611
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2944
-611
lines changed

docs/release_notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
### 🔧 Compatibility Notes
1010

11-
-
11+
- The constructor of the `AssistantMessage` class now takes `List<MessageToolCall>` as input instead of `List<ResponseMessageToolCall>` (the generated class got renamed).
1212

1313
### ✨ New Functionality
1414

orchestration/pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@
3131
</developers>
3232
<properties>
3333
<project.rootdir>${project.basedir}/../</project.rootdir>
34-
<coverage.complexity>83%</coverage.complexity>
34+
<coverage.complexity>84%</coverage.complexity>
3535
<coverage.line>94%</coverage.line>
36-
<coverage.instruction>94%</coverage.instruction>
37-
<coverage.branch>78%</coverage.branch>
38-
<coverage.method>93%</coverage.method>
36+
<coverage.instruction>95%</coverage.instruction>
37+
<coverage.branch>79%</coverage.branch>
38+
<coverage.method>94%</coverage.method>
3939
<coverage.class>100%</coverage.class>
4040
</properties>
4141

orchestration/src/main/java/com/sap/ai/sdk/orchestration/AssistantMessage.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package com.sap.ai.sdk.orchestration;
22

3+
import static com.sap.ai.sdk.orchestration.model.AssistantChatMessage.RoleEnum.ASSISTANT;
4+
35
import com.google.common.annotations.Beta;
6+
import com.sap.ai.sdk.orchestration.model.AssistantChatMessage;
47
import com.sap.ai.sdk.orchestration.model.ChatMessage;
5-
import com.sap.ai.sdk.orchestration.model.ResponseMessageToolCall;
6-
import com.sap.ai.sdk.orchestration.model.SingleChatMessage;
8+
import com.sap.ai.sdk.orchestration.model.ChatMessageContent;
9+
import com.sap.ai.sdk.orchestration.model.MessageToolCall;
10+
import com.sap.ai.sdk.orchestration.model.TextContent;
711
import java.util.List;
812
import javax.annotation.Nonnull;
913
import javax.annotation.Nullable;
@@ -27,7 +31,7 @@ public class AssistantMessage implements Message {
2731
MessageContent content;
2832

2933
/** Tool call if there is any. */
30-
@Nullable List<ResponseMessageToolCall> toolCalls;
34+
@Nullable List<MessageToolCall> toolCalls;
3135

3236
/**
3337
* Creates a new assistant message with the given single message.
@@ -39,12 +43,22 @@ public AssistantMessage(@Nonnull final String singleMessage) {
3943
toolCalls = null;
4044
}
4145

46+
/**
47+
* Creates a new assistant message with the given single message.
48+
*
49+
* @param content the single message.
50+
*/
51+
AssistantMessage(@Nonnull final MessageContent content) {
52+
this.content = content;
53+
toolCalls = null;
54+
}
55+
4256
/**
4357
* Creates a new assistant message with the given tool calls.
4458
*
4559
* @param toolCalls list of tool call objects
4660
*/
47-
public AssistantMessage(@Nonnull final List<ResponseMessageToolCall> toolCalls) {
61+
public AssistantMessage(@Nonnull final List<MessageToolCall> toolCalls) {
4862
content = new MessageContent(List.of());
4963
this.toolCalls = toolCalls;
5064
}
@@ -53,11 +67,15 @@ public AssistantMessage(@Nonnull final List<ResponseMessageToolCall> toolCalls)
5367
@Override
5468
public ChatMessage createChatMessage() {
5569
if (toolCalls() != null) {
56-
// content shouldn't be required for tool calls 🤷
57-
val message = SingleChatMessage.create().role(role).content("");
58-
message.setCustomField("tool_calls", toolCalls);
59-
return message;
70+
return AssistantChatMessage.create().role(ASSISTANT).toolCalls(toolCalls);
6071
}
61-
return Message.super.createChatMessage();
72+
val texts =
73+
content.items().stream()
74+
.filter(item -> item instanceof TextItem)
75+
.map(item -> (TextItem) item)
76+
.map(item -> TextContent.create().type(TextContent.TypeEnum.TEXT).text(item.text()))
77+
.toList();
78+
79+
return AssistantChatMessage.create().role(ASSISTANT).content(ChatMessageContent.create(texts));
6280
}
6381
}

orchestration/src/main/java/com/sap/ai/sdk/orchestration/JacksonMixins.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,28 @@ interface NoneTypeInfoMixin {}
4040
name = "text")
4141
})
4242
interface ResponseFormatSubTypesMixin {}
43+
44+
@JsonTypeInfo(
45+
use = JsonTypeInfo.Id.NAME,
46+
include = JsonTypeInfo.As.PROPERTY,
47+
property = "role",
48+
visible = true)
49+
@JsonSubTypes({
50+
@JsonSubTypes.Type(
51+
value = com.sap.ai.sdk.orchestration.model.AssistantChatMessage.class,
52+
name = "assistant"),
53+
@JsonSubTypes.Type(
54+
value = com.sap.ai.sdk.orchestration.model.DeveloperChatMessage.class,
55+
name = "developer"),
56+
@JsonSubTypes.Type(
57+
value = com.sap.ai.sdk.orchestration.model.SystemChatMessage.class,
58+
name = "system"),
59+
@JsonSubTypes.Type(
60+
value = com.sap.ai.sdk.orchestration.model.ToolChatMessage.class,
61+
name = "tool"),
62+
@JsonSubTypes.Type(
63+
value = com.sap.ai.sdk.orchestration.model.UserChatMessage.class,
64+
name = "user")
65+
})
66+
interface ChatMessageMixin {}
4367
}

orchestration/src/main/java/com/sap/ai/sdk/orchestration/Message.java

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@
22

33
import com.google.common.annotations.Beta;
44
import com.sap.ai.sdk.orchestration.model.ChatMessage;
5-
import com.sap.ai.sdk.orchestration.model.ImageContent;
6-
import com.sap.ai.sdk.orchestration.model.ImageContentImageUrl;
7-
import com.sap.ai.sdk.orchestration.model.MultiChatMessage;
8-
import com.sap.ai.sdk.orchestration.model.MultiChatMessageContent;
9-
import com.sap.ai.sdk.orchestration.model.SingleChatMessage;
10-
import com.sap.ai.sdk.orchestration.model.TextContent;
11-
import java.util.LinkedList;
125
import java.util.List;
136
import javax.annotation.Nonnull;
147

@@ -66,23 +59,7 @@ static SystemMessage system(@Nonnull final String message) {
6659
* @return the corresponding {@code ChatMessage} object.
6760
*/
6861
@Nonnull
69-
default ChatMessage createChatMessage() {
70-
final var itemList = this.content().items();
71-
if (itemList.size() == 1 && itemList.get(0) instanceof TextItem textItem) {
72-
return SingleChatMessage.create().role(role()).content(textItem.text());
73-
}
74-
final var contentList = new LinkedList<MultiChatMessageContent>();
75-
for (final ContentItem item : itemList) {
76-
if (item instanceof TextItem textItem) {
77-
contentList.add(TextContent.create().type(TextContent.TypeEnum.TEXT).text(textItem.text()));
78-
} else if (item instanceof ImageItem imageItem) {
79-
final var detail = imageItem.detailLevel().toString();
80-
final var img = ImageContentImageUrl.create().url(imageItem.imageUrl()).detail(detail);
81-
contentList.add(ImageContent.create().type(ImageContent.TypeEnum.IMAGE_URL).imageUrl(img));
82-
}
83-
}
84-
return MultiChatMessage.create().role(role()).content(contentList);
85-
}
62+
ChatMessage createChatMessage();
8663

8764
/**
8865
* Returns the role of the assistant.
Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.sap.ai.sdk.orchestration;
22

3-
import com.sap.ai.sdk.orchestration.model.ImageContent;
4-
import com.sap.ai.sdk.orchestration.model.MultiChatMessageContent;
5-
import com.sap.ai.sdk.orchestration.model.TextContent;
3+
import com.sap.ai.sdk.orchestration.model.ChatMessageContent;
4+
import com.sap.ai.sdk.orchestration.model.UserChatMessageContent;
5+
import com.sap.ai.sdk.orchestration.model.UserChatMessageContentItem;
6+
import java.util.ArrayList;
67
import java.util.List;
78
import javax.annotation.Nonnull;
9+
import lombok.val;
810

911
/**
1012
* Represents the content of a chat message.
@@ -14,23 +16,39 @@
1416
*/
1517
public record MessageContent(@Nonnull List<ContentItem> items) {
1618
@Nonnull
17-
static MessageContent fromMCMContentList(
18-
@Nonnull final List<MultiChatMessageContent> mCMContentList) {
19-
final var itemList =
20-
mCMContentList.stream()
21-
.map(
22-
content -> {
23-
if (content instanceof TextContent text) {
24-
return new TextItem(text.getText());
25-
} else {
26-
final var imageUrl = ((ImageContent) content).getImageUrl();
27-
return (ContentItem)
28-
new ImageItem(
29-
imageUrl.getUrl(),
30-
ImageItem.DetailLevel.fromString(imageUrl.getDetail()));
31-
}
32-
})
33-
.toList();
34-
return new MessageContent(itemList);
19+
static MessageContent fromChatMessageContent(final ChatMessageContent chatMessageContent) {
20+
if (chatMessageContent instanceof ChatMessageContent.InnerString innerString) {
21+
return new MessageContent(List.of(new TextItem(innerString.value())));
22+
} else if (chatMessageContent
23+
instanceof ChatMessageContent.InnerTextContents innerTextContents) {
24+
val texts =
25+
innerTextContents.values().stream()
26+
.map(textContent -> ((ContentItem) new TextItem(textContent.getText())))
27+
.toList();
28+
return new MessageContent(texts);
29+
}
30+
return new MessageContent(List.of());
31+
}
32+
33+
@Nonnull
34+
static MessageContent fromUserChatMessageContent(
35+
final UserChatMessageContent chatMessageContent) {
36+
if (chatMessageContent instanceof UserChatMessageContent.InnerString innerString) {
37+
return new MessageContent(List.of(new TextItem(innerString.value())));
38+
} else if (chatMessageContent
39+
instanceof
40+
final UserChatMessageContent.InnerUserChatMessageContentItems innerContentItems) {
41+
val items = new ArrayList<ContentItem>();
42+
for (val value : innerContentItems.values()) {
43+
if (value.getType().equals(UserChatMessageContentItem.TypeEnum.TEXT)) {
44+
items.add(new TextItem(value.getText()));
45+
} else if (value.getType().equals(UserChatMessageContentItem.TypeEnum.IMAGE_URL)) {
46+
val detailLevel = ImageItem.DetailLevel.fromString(value.getImageUrl().getDetail());
47+
items.add(new ImageItem(value.getImageUrl().getUrl(), detailLevel));
48+
}
49+
}
50+
return new MessageContent(items);
51+
}
52+
return new MessageContent(List.of());
3553
}
3654
}

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationChatResponse.java

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@
22

33
import static lombok.AccessLevel.PACKAGE;
44

5+
import com.sap.ai.sdk.orchestration.model.AssistantChatMessage;
56
import com.sap.ai.sdk.orchestration.model.ChatMessage;
7+
import com.sap.ai.sdk.orchestration.model.ChatMessageContent;
68
import com.sap.ai.sdk.orchestration.model.CompletionPostResponse;
79
import com.sap.ai.sdk.orchestration.model.LLMChoice;
810
import com.sap.ai.sdk.orchestration.model.LLMModuleResultSynchronous;
9-
import com.sap.ai.sdk.orchestration.model.MultiChatMessage;
10-
import com.sap.ai.sdk.orchestration.model.SingleChatMessage;
11+
import com.sap.ai.sdk.orchestration.model.SystemChatMessage;
1112
import com.sap.ai.sdk.orchestration.model.TokenUsage;
13+
import com.sap.ai.sdk.orchestration.model.ToolChatMessage;
14+
import com.sap.ai.sdk.orchestration.model.UserChatMessage;
1215
import java.util.ArrayList;
1316
import java.util.List;
1417
import javax.annotation.Nonnull;
1518
import lombok.RequiredArgsConstructor;
1619
import lombok.Value;
20+
import lombok.val;
1721

1822
/** Orchestration chat completion output. */
1923
@Value
@@ -57,42 +61,40 @@ public TokenUsage getTokenUsage() {
5761
*/
5862
@Nonnull
5963
public List<Message> getAllMessages() throws IllegalArgumentException {
60-
final var messages = new ArrayList<Message>();
64+
val messages = new ArrayList<Message>();
6165
for (final ChatMessage chatMessage : originalResponse.getModuleResults().getTemplating()) {
62-
if (chatMessage instanceof SingleChatMessage simpleMsg) {
63-
messages.add(chatMessageIntoMessage(simpleMsg));
64-
} else if (chatMessage instanceof MultiChatMessage mCMessage) {
65-
messages.add(chatMessageIntoMessage(mCMessage));
66+
if (chatMessage instanceof AssistantChatMessage assistantChatMessage) {
67+
val toolCalls = assistantChatMessage.getToolCalls();
68+
if (!toolCalls.isEmpty()) {
69+
messages.add(new AssistantMessage(toolCalls));
70+
} else {
71+
messages.add(
72+
new AssistantMessage(
73+
MessageContent.fromChatMessageContent(assistantChatMessage.getContent())));
74+
}
75+
} else if (chatMessage instanceof SystemChatMessage systemChatMessage) {
76+
messages.add(
77+
new SystemMessage(
78+
MessageContent.fromChatMessageContent(systemChatMessage.getContent())));
79+
} else if (chatMessage instanceof UserChatMessage userChatMessage) {
80+
messages.add(
81+
new UserMessage(
82+
MessageContent.fromUserChatMessageContent(userChatMessage.getContent())));
83+
} else if (chatMessage instanceof ToolChatMessage toolChatMessage) {
84+
messages.add(
85+
new ToolMessage(
86+
toolChatMessage.getToolCallId(),
87+
((ChatMessageContent.InnerString) toolChatMessage.getContent()).value()));
6688
} else {
6789
throw new IllegalArgumentException(
6890
"Messages of type " + chatMessage.getClass() + " are not supported by convenience API");
6991
}
7092
}
93+
7194
messages.add(Message.assistant(getChoice().getMessage().getContent()));
7295
return messages;
7396
}
7497

75-
@Nonnull
76-
private Message chatMessageIntoMessage(@Nonnull final SingleChatMessage simpleMsg) {
77-
return switch (simpleMsg.getRole()) {
78-
case "user" -> Message.user(simpleMsg.getContent());
79-
case "assistant" -> Message.assistant(simpleMsg.getContent());
80-
case "system" -> Message.system(simpleMsg.getContent());
81-
default -> throw new IllegalStateException("Unexpected role: " + simpleMsg.getRole());
82-
};
83-
}
84-
85-
@Nonnull
86-
private Message chatMessageIntoMessage(@Nonnull final MultiChatMessage mCMessage) {
87-
return switch (mCMessage.getRole()) {
88-
case "user" -> new UserMessage(MessageContent.fromMCMContentList(mCMessage.getContent()));
89-
case "system" -> new SystemMessage(MessageContent.fromMCMContentList(mCMessage.getContent()));
90-
default ->
91-
throw new IllegalStateException(
92-
"Unexpected role with complex message: " + mCMessage.getRole());
93-
};
94-
}
95-
9698
/**
9799
* Get the LLM response. Useful for accessing the finish reason or further data like logprobs.
98100
*

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationJacksonConfiguration.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,13 @@ public static ObjectMapper getOrchestrationObjectMapper() {
4242
jackson.addMixIn(
4343
ModuleResultsOutputUnmaskingInner.class,
4444
JacksonMixins.ModuleResultsOutputUnmaskingInnerMixIn.class);
45+
jackson.addMixIn(ChatMessage.class, JacksonMixins.ChatMessageMixin.class);
4546

4647
final var module =
4748
new SimpleModule()
48-
.addDeserializer(
49-
ChatMessage.class,
50-
PolymorphicFallbackDeserializer.fromJsonSubTypes(ChatMessage.class))
5149
.addDeserializer(
5250
TemplateResponseFormat.class,
5351
PolymorphicFallbackDeserializer.fromJsonSubTypes(TemplateResponseFormat.class))
54-
.setMixInAnnotation(ChatMessage.class, JacksonMixins.NoneTypeInfoMixin.class)
5552
.setMixInAnnotation(
5653
TemplateResponseFormat.class, JacksonMixins.ResponseFormatSubTypesMixin.class);
5754
jackson.registerModule(module);

orchestration/src/main/java/com/sap/ai/sdk/orchestration/SystemMessage.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package com.sap.ai.sdk.orchestration;
22

3+
import static com.sap.ai.sdk.orchestration.model.SystemChatMessage.RoleEnum.SYSTEM;
4+
35
import com.google.common.annotations.Beta;
6+
import com.sap.ai.sdk.orchestration.model.ChatMessage;
7+
import com.sap.ai.sdk.orchestration.model.ChatMessageContent;
8+
import com.sap.ai.sdk.orchestration.model.SystemChatMessage;
9+
import com.sap.ai.sdk.orchestration.model.TextContent;
410
import java.util.LinkedList;
511
import java.util.List;
612
import javax.annotation.Nonnull;
@@ -10,6 +16,7 @@
1016
import lombok.Value;
1117
import lombok.experimental.Accessors;
1218
import lombok.experimental.Tolerate;
19+
import lombok.val;
1320

1421
/** Represents a chat message as 'system' to the orchestration service. */
1522
@Value
@@ -48,4 +55,16 @@ public SystemMessage withText(@Nonnull final String message) {
4855
contentItems.add(new TextItem(message));
4956
return new SystemMessage(new MessageContent(contentItems));
5057
}
58+
59+
@Nonnull
60+
@Override
61+
public ChatMessage createChatMessage() {
62+
val texts =
63+
content.items().stream()
64+
.filter(item -> item instanceof TextItem)
65+
.map(item -> (TextItem) item)
66+
.map(item -> TextContent.create().type(TextContent.TypeEnum.TEXT).text(item.text()))
67+
.toList();
68+
return SystemChatMessage.create().role(SYSTEM).content(ChatMessageContent.create(texts));
69+
}
5170
}

0 commit comments

Comments
 (0)