Skip to content

Commit 9691c9c

Browse files
.NET: Fix role assignment in ChatMessage construction (#4290)
* Use actual message role when creating ChatMessage Replace hard-coded ChatRole.User with a ChatRole constructed from the message's Role. The change ensures ToChatMessage and FunctionMessage use the original role (new ChatRole(this.Role)) for both text and contents branches, fixing incorrect role assignment when constructing ChatMessage instances. * Update changes * Fix formatting in ToChatMessage tests --------- Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
1 parent d2977d6 commit 9691c9c

2 files changed

Lines changed: 122 additions & 3 deletions

File tree

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/Models/ChatCompletionRequestMessage.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,16 @@ internal abstract record ChatCompletionRequestMessage
3939
/// <exception cref="InvalidOperationException">Thrown when the content is neither text nor AI contents.</exception>
4040
public virtual ChatMessage ToChatMessage()
4141
{
42+
var role = new ChatRole(this.Role);
43+
4244
if (this.Content.IsText)
4345
{
44-
return new(ChatRole.User, this.Content.Text);
46+
return new(role, this.Content.Text);
4547
}
4648
else if (this.Content.IsContents)
4749
{
4850
var aiContents = this.Content.Contents.Select(MessageContentPartConverter.ToAIContent).Where(c => c is not null).ToList();
49-
return new ChatMessage(ChatRole.User, aiContents!);
51+
return new ChatMessage(role, aiContents!);
5052
}
5153

5254
throw new InvalidOperationException("MessageContent has no value");
@@ -165,9 +167,11 @@ internal sealed record FunctionMessage : ChatCompletionRequestMessage
165167
/// <exception cref="InvalidOperationException">Thrown when the content is not text.</exception>
166168
public override ChatMessage ToChatMessage()
167169
{
170+
var role = new ChatRole(this.Role);
171+
168172
if (this.Content.IsText)
169173
{
170-
return new(ChatRole.User, this.Content.Text);
174+
return new(role, this.Content.Text);
171175
}
172176

173177
throw new InvalidOperationException("FunctionMessage Content must be text");
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Linq;
4+
using System.Text.Json;
5+
using Microsoft.Agents.AI.Hosting.OpenAI.ChatCompletions.Models;
6+
using Microsoft.Extensions.AI;
7+
8+
namespace Microsoft.Agents.AI.Hosting.OpenAI.UnitTests;
9+
10+
/// <summary>
11+
/// Tests for ChatCompletionRequestMessage.ToChatMessage() role preservation.
12+
/// Verifies that each message type correctly maps its role to the corresponding ChatRole.
13+
/// </summary>
14+
public sealed class ChatCompletionRequestMessageToChatMessageTests
15+
{
16+
[Theory]
17+
[InlineData("system", """{"role":"system","content":"You are a helpful assistant."}""")]
18+
[InlineData("developer", """{"role":"developer","content":"Follow these rules."}""")]
19+
[InlineData("user", """{"role":"user","content":"Hello!"}""")]
20+
[InlineData("assistant", """{"role":"assistant","content":"Hi there!"}""")]
21+
[InlineData("tool", """{"role":"tool","content":"result","tool_call_id":"call_123"}""")]
22+
public void ToChatMessage_PreservesRole_ForTextContent(string expectedRole, string json)
23+
{
24+
// Arrange
25+
ChatCompletionRequestMessage message = JsonSerializer.Deserialize(
26+
json, ChatCompletions.ChatCompletionsJsonContext.Default.ChatCompletionRequestMessage)!;
27+
28+
// Act
29+
ChatMessage chatMessage = message.ToChatMessage();
30+
31+
// Assert
32+
Assert.Equal(expectedRole, message.Role);
33+
Assert.Equal(new ChatRole(expectedRole), chatMessage.Role);
34+
}
35+
36+
[Fact]
37+
public void ToChatMessage_FunctionMessage_PreservesRole()
38+
{
39+
// Arrange
40+
const string Json = """{"role":"function","name":"get_weather","content":"sunny"}""";
41+
ChatCompletionRequestMessage message = JsonSerializer.Deserialize(
42+
Json, ChatCompletions.ChatCompletionsJsonContext.Default.ChatCompletionRequestMessage)!;
43+
44+
// Act
45+
ChatMessage chatMessage = message.ToChatMessage();
46+
47+
// Assert
48+
Assert.Equal("function", message.Role);
49+
Assert.Equal(new ChatRole("function"), chatMessage.Role);
50+
}
51+
52+
[Theory]
53+
[InlineData("system")]
54+
[InlineData("developer")]
55+
[InlineData("user")]
56+
[InlineData("assistant")]
57+
public void ToChatMessage_PreservesRole_ForMultiPartContent(string expectedRole)
58+
{
59+
// Arrange
60+
string json = $$"""{"role":"{{expectedRole}}","content":[{"type":"text","text":"Hello!"}]}""";
61+
ChatCompletionRequestMessage message = JsonSerializer.Deserialize(
62+
json, ChatCompletions.ChatCompletionsJsonContext.Default.ChatCompletionRequestMessage)!;
63+
64+
// Act
65+
ChatMessage chatMessage = message.ToChatMessage();
66+
67+
// Assert
68+
Assert.Equal(expectedRole, message.Role);
69+
Assert.Equal(new ChatRole(expectedRole), chatMessage.Role);
70+
}
71+
72+
[Fact]
73+
public void ToChatMessage_MultiTurnConversation_PreservesAllRoles()
74+
{
75+
// Arrange - simulate a multi-turn conversation
76+
string[] jsons =
77+
[
78+
"""{"role":"system","content":"You are a helpful assistant."}""",
79+
"""{"role":"user","content":"Hello!"}""",
80+
"""{"role":"assistant","content":"Hi there! How can I help?"}""",
81+
"""{"role":"user","content":"What did I just say?"}"""
82+
];
83+
84+
string[] expectedRoles = ["system", "user", "assistant", "user"];
85+
86+
// Act
87+
ChatMessage[] chatMessages = jsons
88+
.Select(j => JsonSerializer.Deserialize(
89+
j, ChatCompletions.ChatCompletionsJsonContext.Default.ChatCompletionRequestMessage)!)
90+
.Select(m => m.ToChatMessage())
91+
.ToArray();
92+
93+
// Assert
94+
Assert.Equal(expectedRoles.Length, chatMessages.Length);
95+
for (int i = 0; i < expectedRoles.Length; i++)
96+
{
97+
Assert.Equal(new ChatRole(expectedRoles[i]), chatMessages[i].Role);
98+
}
99+
}
100+
101+
[Fact]
102+
public void ToChatMessage_PreservesTextContent()
103+
{
104+
// Arrange
105+
const string Json = """{"role":"system","content":"You are a helpful assistant."}""";
106+
ChatCompletionRequestMessage message = JsonSerializer.Deserialize(
107+
Json, ChatCompletions.ChatCompletionsJsonContext.Default.ChatCompletionRequestMessage)!;
108+
109+
// Act
110+
ChatMessage chatMessage = message.ToChatMessage();
111+
112+
// Assert
113+
Assert.Contains(chatMessage.Contents, c => c is TextContent tc && tc.Text == "You are a helpful assistant.");
114+
}
115+
}

0 commit comments

Comments
 (0)