Skip to content

Commit f41c700

Browse files
jpalvarezlmssfang
andauthored
Fix: serialization and deserialization of ChatRequestMessage subclasses (Azure#44674)
* Added fix for userMessage and toolMessage * Fixed previous tests assertions * Added tests for assistant message types * Fixed and added test coverage for developer message * Added coverage for Function request messages * Fix and test coverage for ChatRequestSystemMessage * checks WIP * Replaced text blocks with string * Adding update to the changelog * Removed @generated annotation for (de)serialization methods for Developer variant * Added more utility constructors * Update sdk/openai/azure-ai-openai/src/main/java/com/azure/ai/openai/models/ChatRequestDeveloperMessage.java Co-authored-by: Shawn Fang <[email protected]> * Update sdk/openai/azure-ai-openai/src/main/java/com/azure/ai/openai/models/ChatRequestSystemMessage.java Co-authored-by: Shawn Fang <[email protected]> --------- Co-authored-by: Shawn Fang <[email protected]>
1 parent fa0cb9b commit f41c700

File tree

7 files changed

+842
-52
lines changed

7 files changed

+842
-52
lines changed

sdk/openai/azure-ai-openai/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
### Bugs Fixed
1212

13+
- The serialization and the subsequent serialization of any subclass of `ChatRequestMessage` (namely, `ChatRequestAssistantMessage`, `ChatRequestDeveloperMessage`, `ChatRequestFunctionMessage`, `ChatRequestSystemMessage`, `ChatRequestToolMessage`, `ChatRequestUserMessage`) was faulty. The `content` member of most of these classes was lost. Related issues are [42882](https://github.com/Azure/azure-sdk-for-java/issues/42882) and [44094](https://github.com/Azure/azure-sdk-for-java/issues/44094).
14+
1315
### Other Changes
1416

1517
## 1.0.0-beta.15 (2025-03-14)

sdk/openai/azure-ai-openai/src/main/java/com/azure/ai/openai/models/ChatRequestAssistantMessage.java

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
import com.azure.core.annotation.Fluent;
77
import com.azure.core.annotation.Generated;
88
import com.azure.core.util.BinaryData;
9+
import com.azure.core.util.CoreUtils;
910
import com.azure.json.JsonReader;
1011
import com.azure.json.JsonToken;
1112
import com.azure.json.JsonWriter;
1213
import java.io.IOException;
14+
import java.util.Arrays;
1315
import java.util.List;
1416

1517
/**
@@ -26,7 +28,7 @@ public final class ChatRequestAssistantMessage extends ChatRequestMessage {
2628

2729
private final String stringContent;
2830

29-
private final List<ChatMessageContentItem> chatMessageContentItem;
31+
private final List<ChatMessageContentItem> chatMessageContentItems;
3032

3133
/*
3234
* An optional name for the participant.
@@ -58,6 +60,38 @@ public BinaryData getContent() {
5860
return this.content;
5961
}
6062

63+
/**
64+
* Get the content property: The contents of the user message, with available input types varying by selected model.
65+
* If the result of this method is `null`, it means that the content could be a String or null altogether.
66+
*
67+
* @return the content value if defined as a list
68+
*/
69+
public List<ChatMessageContentItem> getListContent() {
70+
return this.chatMessageContentItems;
71+
}
72+
73+
/**
74+
* Get the content property: The contents of the user message, with available input types varying by selected model.
75+
* If the result of this method is `null`, it means that the content could be a String or null altogether.
76+
*
77+
* @return the content value if defined as an array
78+
*/
79+
public ChatMessageContentItem[] getArrayContent() {
80+
return this.chatMessageContentItems == null
81+
? null
82+
: this.chatMessageContentItems.toArray(new ChatMessageContentItem[0]);
83+
}
84+
85+
/**
86+
* Get the content property: The contents of the user message, with available input types varying by selected model.
87+
* If the result of this method is `null`, it means that the content could be a list or null altogether.
88+
*
89+
* @return the content value if defined as a string
90+
*/
91+
public String getStringContent() {
92+
return this.stringContent;
93+
}
94+
6195
/**
6296
* Get the name property: An optional name for the participant.
6397
*
@@ -157,8 +191,10 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
157191
jsonWriter.writeStartObject();
158192
if (stringContent != null) {
159193
jsonWriter.writeStringField("content", stringContent);
160-
} else if (chatMessageContentItem != null) {
161-
jsonWriter.writeArrayField("content", chatMessageContentItem, JsonWriter::writeJson);
194+
} else if (chatMessageContentItems != null) {
195+
jsonWriter.writeArrayField("content", chatMessageContentItems, JsonWriter::writeJson);
196+
} else {
197+
jsonWriter.writeNullField("content");
162198
}
163199
jsonWriter.writeStringField("role", this.role == null ? null : this.role.toString());
164200
jsonWriter.writeStringField("refusal", this.refusal);
@@ -180,6 +216,8 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
180216
public static ChatRequestAssistantMessage fromJson(JsonReader jsonReader) throws IOException {
181217
return jsonReader.readObject(reader -> {
182218
BinaryData content = null;
219+
String stringContent = null;
220+
List<ChatMessageContentItem> chatMessageContentItems = null;
183221
ChatRole role = ChatRole.ASSISTANT;
184222
String refusal = null;
185223
String name = null;
@@ -190,10 +228,10 @@ public static ChatRequestAssistantMessage fromJson(JsonReader jsonReader) throws
190228
reader.nextToken();
191229
if ("content".equals(fieldName)) {
192230
if (reader.currentToken() == JsonToken.STRING) {
193-
content = BinaryData.fromString(reader.getString());
231+
stringContent = reader.getString();
194232
} else if (reader.currentToken() == JsonToken.START_ARRAY) {
195-
content = BinaryData.fromObject(
196-
reader.readArray(arrayReader -> arrayReader.readObject(ChatMessageContentItem::fromJson)));
233+
chatMessageContentItems
234+
= reader.readArray(arrayReader -> arrayReader.readObject(ChatMessageContentItem::fromJson));
197235
} else if (reader.currentToken() == JsonToken.NULL) {
198236
content = null;
199237
} else {
@@ -214,8 +252,14 @@ public static ChatRequestAssistantMessage fromJson(JsonReader jsonReader) throws
214252
reader.skipChildren();
215253
}
216254
}
217-
ChatRequestAssistantMessage deserializedChatRequestAssistantMessage
218-
= new ChatRequestAssistantMessage(content);
255+
ChatRequestAssistantMessage deserializedChatRequestAssistantMessage;
256+
if (CoreUtils.isNullOrEmpty(stringContent) && chatMessageContentItems == null) {
257+
deserializedChatRequestAssistantMessage = new ChatRequestAssistantMessage(content);
258+
} else {
259+
deserializedChatRequestAssistantMessage = CoreUtils.isNullOrEmpty(stringContent)
260+
? new ChatRequestAssistantMessage(chatMessageContentItems)
261+
: new ChatRequestAssistantMessage(stringContent);
262+
}
219263
deserializedChatRequestAssistantMessage.role = role;
220264
deserializedChatRequestAssistantMessage.setRefusal(refusal)
221265
.setName(name)
@@ -233,7 +277,7 @@ public static ChatRequestAssistantMessage fromJson(JsonReader jsonReader) throws
233277
private ChatRequestAssistantMessage(BinaryData content) {
234278
this.content = content;
235279
this.stringContent = null;
236-
this.chatMessageContentItem = null;
280+
this.chatMessageContentItems = null;
237281
}
238282

239283
/**
@@ -244,7 +288,7 @@ private ChatRequestAssistantMessage(BinaryData content) {
244288
public ChatRequestAssistantMessage(String content) {
245289
this.content = content == null ? null : BinaryData.fromString(content);
246290
this.stringContent = content;
247-
this.chatMessageContentItem = null;
291+
this.chatMessageContentItems = null;
248292
}
249293

250294
/**
@@ -255,7 +299,18 @@ public ChatRequestAssistantMessage(String content) {
255299
public ChatRequestAssistantMessage(List<ChatMessageContentItem> content) {
256300
this.content = BinaryData.fromObject(content);
257301
this.stringContent = null;
258-
this.chatMessageContentItem = content;
302+
this.chatMessageContentItems = content;
303+
}
304+
305+
/**
306+
* Creates a new instance of ChatRequestAssistantMessage using a collection of structured content.
307+
*
308+
* @param content The collection of structured content associated with the message.
309+
*/
310+
public ChatRequestAssistantMessage(ChatMessageContentItem[] content) {
311+
this.content = BinaryData.fromObject(content);
312+
this.chatMessageContentItems = Arrays.asList(content);
313+
this.stringContent = null;
259314
}
260315

261316
/*

sdk/openai/azure-ai-openai/src/main/java/com/azure/ai/openai/models/ChatRequestDeveloperMessage.java

Lines changed: 103 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
import com.azure.core.annotation.Fluent;
77
import com.azure.core.annotation.Generated;
88
import com.azure.core.util.BinaryData;
9+
import com.azure.core.util.CoreUtils;
910
import com.azure.json.JsonReader;
1011
import com.azure.json.JsonToken;
1112
import com.azure.json.JsonWriter;
1213
import java.io.IOException;
14+
import java.util.Arrays;
15+
import java.util.List;
1316

1417
/**
1518
* Developer-provided instructions that the model should follow, regardless of messages sent by the user.
@@ -30,6 +33,10 @@ public final class ChatRequestDeveloperMessage extends ChatRequestMessage {
3033
@Generated
3134
private final BinaryData content;
3235

36+
private final String stringContent;
37+
38+
private final List<ChatMessageContentItem> chatMessageContentItems;
39+
3340
/*
3441
* An optional name for the participant. Provides the model information to differentiate between participants of the
3542
* same role.
@@ -42,9 +49,43 @@ public final class ChatRequestDeveloperMessage extends ChatRequestMessage {
4249
*
4350
* @param content the content value to set.
4451
*/
45-
@Generated
46-
public ChatRequestDeveloperMessage(BinaryData content) {
52+
private ChatRequestDeveloperMessage(BinaryData content) {
4753
this.content = content;
54+
this.stringContent = null;
55+
this.chatMessageContentItems = null;
56+
}
57+
58+
/**
59+
* Creates an instance of ChatRequestDeveloperMessage class.
60+
*
61+
* @param content the content value to set.
62+
*/
63+
public ChatRequestDeveloperMessage(String content) {
64+
this.content = BinaryData.fromString(content);
65+
this.stringContent = content;
66+
this.chatMessageContentItems = null;
67+
}
68+
69+
/**
70+
* Creates an instance of ChatRequestDeveloperMessage class.
71+
*
72+
* @param content the content value to set.
73+
*/
74+
public ChatRequestDeveloperMessage(List<ChatMessageContentItem> content) {
75+
this.content = BinaryData.fromObject(content);
76+
this.stringContent = null;
77+
this.chatMessageContentItems = content;
78+
}
79+
80+
/**
81+
* Creates a new instance of ChatRequestDeveloperMessage using a collection of structured content.
82+
*
83+
* @param content The collection of structured content associated with the message.
84+
*/
85+
public ChatRequestDeveloperMessage(ChatMessageContentItem[] content) {
86+
this.content = BinaryData.fromObject(content);
87+
this.chatMessageContentItems = Arrays.asList(content);
88+
this.stringContent = null;
4889
}
4990

5091
/**
@@ -69,6 +110,38 @@ public BinaryData getContent() {
69110
return this.content;
70111
}
71112

113+
/**
114+
* Get the content property: The contents of the user message, with available input types varying by selected model.
115+
* If the result of this method is `null`, it means that the content could be a String or null altogether.
116+
*
117+
* @return the content value if defined as a list
118+
*/
119+
public List<ChatMessageContentItem> getListContent() {
120+
return this.chatMessageContentItems;
121+
}
122+
123+
/**
124+
* Get the content property: The contents of the user message, with available input types varying by selected model.
125+
* If the result of this method is `null`, it means that the content could be a String or null altogether.
126+
*
127+
* @return the content value if defined as an array
128+
*/
129+
public ChatMessageContentItem[] getArrayContent() {
130+
return this.chatMessageContentItems == null
131+
? null
132+
: this.chatMessageContentItems.toArray(new ChatMessageContentItem[0]);
133+
}
134+
135+
/**
136+
* Get the content property: The contents of the user message, with available input types varying by selected model.
137+
* If the result of this method is `null`, it means that the content could be a list or null altogether.
138+
*
139+
* @return the content value if defined as a string
140+
*/
141+
public String getStringContent() {
142+
return this.stringContent;
143+
}
144+
72145
/**
73146
* Get the name property: An optional name for the participant. Provides the model information to differentiate
74147
* between participants of the same role.
@@ -96,12 +169,16 @@ public ChatRequestDeveloperMessage setName(String name) {
96169
/**
97170
* {@inheritDoc}
98171
*/
99-
@Generated
100172
@Override
101173
public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
102174
jsonWriter.writeStartObject();
103-
jsonWriter.writeFieldName("content");
104-
this.content.writeTo(jsonWriter);
175+
if (stringContent != null) {
176+
jsonWriter.writeStringField("content", stringContent);
177+
} else if (chatMessageContentItems != null) {
178+
jsonWriter.writeArrayField("content", chatMessageContentItems, JsonWriter::writeJson);
179+
} else {
180+
jsonWriter.writeNullField("content");
181+
}
105182
jsonWriter.writeStringField("role", this.role == null ? null : this.role.toString());
106183
jsonWriter.writeStringField("name", this.name);
107184
return jsonWriter.writeEndObject();
@@ -116,17 +193,28 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException {
116193
* @throws IllegalStateException If the deserialized JSON object was missing any required properties.
117194
* @throws IOException If an error occurs while reading the ChatRequestDeveloperMessage.
118195
*/
119-
@Generated
120196
public static ChatRequestDeveloperMessage fromJson(JsonReader jsonReader) throws IOException {
121197
return jsonReader.readObject(reader -> {
122198
BinaryData content = null;
199+
String stringContent = null;
200+
List<ChatMessageContentItem> chatMessageContentItems = null;
123201
ChatRole role = ChatRole.DEVELOPER;
124202
String name = null;
125203
while (reader.nextToken() != JsonToken.END_OBJECT) {
126204
String fieldName = reader.getFieldName();
127205
reader.nextToken();
128206
if ("content".equals(fieldName)) {
129-
content = reader.getNullable(nonNullReader -> BinaryData.fromObject(nonNullReader.readUntyped()));
207+
if (reader.currentToken() == JsonToken.STRING) {
208+
stringContent = reader.getString();
209+
} else if (reader.currentToken() == JsonToken.START_ARRAY) {
210+
chatMessageContentItems
211+
= reader.readArray(arrayReader -> arrayReader.readObject(ChatMessageContentItem::fromJson));
212+
} else if (reader.currentToken() == JsonToken.NULL) {
213+
content = null;
214+
} else {
215+
throw new IllegalStateException("Unexpected 'content' type found when deserializing"
216+
+ " ChatRequestAssistantMessage JSON object: " + reader.currentToken());
217+
}
130218
} else if ("role".equals(fieldName)) {
131219
role = ChatRole.fromString(reader.getString());
132220
} else if ("name".equals(fieldName)) {
@@ -135,8 +223,14 @@ public static ChatRequestDeveloperMessage fromJson(JsonReader jsonReader) throws
135223
reader.skipChildren();
136224
}
137225
}
138-
ChatRequestDeveloperMessage deserializedChatRequestDeveloperMessage
139-
= new ChatRequestDeveloperMessage(content);
226+
ChatRequestDeveloperMessage deserializedChatRequestDeveloperMessage;
227+
if (CoreUtils.isNullOrEmpty(stringContent) && chatMessageContentItems == null) {
228+
deserializedChatRequestDeveloperMessage = new ChatRequestDeveloperMessage(content);
229+
} else {
230+
deserializedChatRequestDeveloperMessage = CoreUtils.isNullOrEmpty(stringContent)
231+
? new ChatRequestDeveloperMessage(chatMessageContentItems)
232+
: new ChatRequestDeveloperMessage(stringContent);
233+
}
140234
deserializedChatRequestDeveloperMessage.role = role;
141235
deserializedChatRequestDeveloperMessage.name = name;
142236
return deserializedChatRequestDeveloperMessage;

0 commit comments

Comments
 (0)