Skip to content

Commit 6ff495d

Browse files
LittleLittleCloudvictordibia
authored andcommitted
[.Net] Mark Message as obsolete and add ToolCallAggregateMessage type (#2716)
* make Message obsolete * add ToolCallAggregateMessage * update message.md * address comment * fix tests * set round to 1 temporarily * revert change * fix test * fix test
1 parent 30876e2 commit 6ff495d

30 files changed

+452
-462
lines changed

dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public async Task CreateOpenAIChatAgentAsync()
8484
new TextMessage(Role.Assistant, "Hello", from: "user"),
8585
],
8686
from: "user"),
87-
new Message(Role.Assistant, "Hello", from: "user"), // Message type is going to be deprecated, please use TextMessage instead
87+
new TextMessage(Role.Assistant, "Hello", from: "user"), // Message type is going to be deprecated, please use TextMessage instead
8888
};
8989

9090
foreach (var message in messages)

dotnet/sample/AutoGen.BasicSamples/Example03_Agent_FunctionCall.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -77,26 +77,26 @@ public static async Task RunAsync()
7777
// talk to the assistant agent
7878
var upperCase = await agent.SendAsync("convert to upper case: hello world");
7979
upperCase.GetContent()?.Should().Be("HELLO WORLD");
80-
upperCase.Should().BeOfType<AggregateMessage<ToolCallMessage, ToolCallResultMessage>>();
80+
upperCase.Should().BeOfType<ToolCallAggregateMessage>();
8181
upperCase.GetToolCalls().Should().HaveCount(1);
8282
upperCase.GetToolCalls().First().FunctionName.Should().Be(nameof(UpperCase));
8383

8484
var concatString = await agent.SendAsync("concatenate strings: a, b, c, d, e");
8585
concatString.GetContent()?.Should().Be("a b c d e");
86-
concatString.Should().BeOfType<AggregateMessage<ToolCallMessage, ToolCallResultMessage>>();
86+
concatString.Should().BeOfType<ToolCallAggregateMessage>();
8787
concatString.GetToolCalls().Should().HaveCount(1);
8888
concatString.GetToolCalls().First().FunctionName.Should().Be(nameof(ConcatString));
8989

9090
var calculateTax = await agent.SendAsync("calculate tax: 100, 0.1");
9191
calculateTax.GetContent().Should().Be("tax is 10");
92-
calculateTax.Should().BeOfType<AggregateMessage<ToolCallMessage, ToolCallResultMessage>>();
92+
calculateTax.Should().BeOfType<ToolCallAggregateMessage>();
9393
calculateTax.GetToolCalls().Should().HaveCount(1);
9494
calculateTax.GetToolCalls().First().FunctionName.Should().Be(nameof(CalculateTax));
9595

9696
// parallel function calls
9797
var calculateTaxes = await agent.SendAsync("calculate tax: 100, 0.1; calculate tax: 200, 0.2");
9898
calculateTaxes.GetContent().Should().Be("tax is 10\ntax is 40"); // "tax is 10\n tax is 40
99-
calculateTaxes.Should().BeOfType<AggregateMessage<ToolCallMessage, ToolCallResultMessage>>();
99+
calculateTaxes.Should().BeOfType<ToolCallAggregateMessage>();
100100
calculateTaxes.GetToolCalls().Should().HaveCount(2);
101101
calculateTaxes.GetToolCalls().First().FunctionName.Should().Be(nameof(CalculateTax));
102102

dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public static async Task RunAsync()
4040
name: "groupAdmin",
4141
systemMessage: "You are the admin of the group chat",
4242
temperature: 0f,
43-
config: gptConfig);
43+
config: gptConfig)
44+
.RegisterPrintMessage();
4445

4546
var userProxy = new UserProxyAgent(name: "user", defaultReply: GroupChatExtension.TERMINATE, humanInputMode: HumanInputMode.NEVER)
4647
.RegisterPrintMessage();

dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs

+28-46
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Example05_Dalle_And_GPT4V.cs
33

4-
using AutoGen;
54
using AutoGen.Core;
5+
using AutoGen.OpenAI;
6+
using AutoGen.OpenAI.Extension;
67
using Azure.AI.OpenAI;
78
using FluentAssertions;
89
using autogen = AutoGen.LLMConfigAPI;
@@ -66,50 +67,39 @@ public static async Task RunAsync()
6667
File.Delete(imagePath);
6768
}
6869

69-
var dalleAgent = new AssistantAgent(
70-
name: "dalle",
71-
systemMessage: "You are a DALL-E agent that generate image from prompt, when conversation is terminated, return the most recent image url",
72-
llmConfig: new ConversableAgentConfig
73-
{
74-
Temperature = 0,
75-
ConfigList = gpt35Config,
76-
FunctionContracts = new[]
77-
{
78-
instance.GenerateImageFunctionContract,
79-
},
80-
},
70+
var generateImageFunctionMiddleware = new FunctionCallMiddleware(
71+
functions: [instance.GenerateImageFunctionContract],
8172
functionMap: new Dictionary<string, Func<string, Task<string>>>
8273
{
8374
{ nameof(GenerateImage), instance.GenerateImageWrapper },
84-
})
75+
});
76+
var dalleAgent = new OpenAIChatAgent(
77+
openAIClient: openAIClient,
78+
modelName: "gpt-3.5-turbo",
79+
name: "dalle",
80+
systemMessage: "You are a DALL-E agent that generate image from prompt, when conversation is terminated, return the most recent image url")
81+
.RegisterMessageConnector()
82+
.RegisterStreamingMiddleware(generateImageFunctionMiddleware)
8583
.RegisterMiddleware(async (msgs, option, agent, ct) =>
8684
{
87-
// if last message contains [TERMINATE], then find the last image url and terminate the conversation
88-
if (msgs.Last().GetContent()?.Contains("TERMINATE") is true)
85+
if (msgs.Any(msg => msg.GetContent()?.ToLower().Contains("approve") is true))
8986
{
90-
var lastMessageWithImage = msgs.Last(msg => msg is ImageMessage) as ImageMessage;
91-
var lastImageUrl = lastMessageWithImage.Url;
92-
Console.WriteLine($"download image from {lastImageUrl} to {imagePath}");
93-
var httpClient = new HttpClient();
94-
var imageBytes = await httpClient.GetByteArrayAsync(lastImageUrl);
95-
File.WriteAllBytes(imagePath, imageBytes);
96-
97-
var messageContent = $@"{GroupChatExtension.TERMINATE}
98-
99-
{lastImageUrl}";
100-
return new TextMessage(Role.Assistant, messageContent)
101-
{
102-
From = "dalle",
103-
};
87+
return new TextMessage(Role.Assistant, $"The image satisfies the condition, conversation is terminated. {GroupChatExtension.TERMINATE}");
10488
}
10589

106-
var reply = await agent.GenerateReplyAsync(msgs, option, ct);
90+
var msgsWithoutImage = msgs.Where(msg => msg is not ImageMessage).ToList();
91+
var reply = await agent.GenerateReplyAsync(msgsWithoutImage, option, ct);
10792

10893
if (reply.GetContent() is string content && content.Contains("IMAGE_GENERATION"))
10994
{
11095
var imageUrl = content.Split("\n").Last();
11196
var imageMessage = new ImageMessage(Role.Assistant, imageUrl, from: reply.From);
11297

98+
Console.WriteLine($"download image from {imageUrl} to {imagePath}");
99+
var httpClient = new HttpClient();
100+
var imageBytes = await httpClient.GetByteArrayAsync(imageUrl, ct);
101+
File.WriteAllBytes(imagePath, imageBytes);
102+
113103
return imageMessage;
114104
}
115105
else
@@ -119,33 +109,25 @@ public static async Task RunAsync()
119109
})
120110
.RegisterPrintMessage();
121111

122-
var gpt4VAgent = new AssistantAgent(
112+
var gpt4VAgent = new OpenAIChatAgent(
113+
openAIClient: openAIClient,
123114
name: "gpt4v",
115+
modelName: "gpt-4-vision-preview",
124116
systemMessage: @"You are a critism that provide feedback to DALL-E agent.
125117
Carefully check the image generated by DALL-E agent and provide feedback.
126-
If the image satisfies the condition, then terminate the conversation by saying [TERMINATE].
118+
If the image satisfies the condition, then say [APPROVE].
127119
Otherwise, provide detailed feedback to DALL-E agent so it can generate better image.
128120
129121
The image should satisfy the following conditions:
130122
- There should be a cat and a mouse in the image
131-
- The cat should be chasing after the mouse
132-
",
133-
llmConfig: new ConversableAgentConfig
134-
{
135-
Temperature = 0,
136-
ConfigList = gpt4vConfig,
137-
})
123+
- The cat should be chasing after the mouse")
124+
.RegisterMessageConnector()
138125
.RegisterPrintMessage();
139126

140-
IEnumerable<IMessage> conversation = new List<IMessage>()
141-
{
142-
new TextMessage(Role.User, "Hey dalle, please generate image from prompt: English short hair blue cat chase after a mouse")
143-
};
144-
var maxRound = 20;
145127
await gpt4VAgent.InitiateChatAsync(
146128
receiver: dalleAgent,
147129
message: "Hey dalle, please generate image from prompt: English short hair blue cat chase after a mouse",
148-
maxRound: maxRound);
130+
maxRound: 10);
149131

150132
File.Exists(imagePath).Should().BeTrue();
151133
}

dotnet/src/AutoGen.Core/Extension/MessageExtension.cs

+20-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// MessageExtension.cs
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Linq;
67
using System.Text;
@@ -15,7 +16,9 @@ public static string FormatMessage(this IMessage message)
1516
{
1617
return message switch
1718
{
19+
#pragma warning disable CS0618 // deprecated
1820
Message msg => msg.FormatMessage(),
21+
#pragma warning restore CS0618 // deprecated
1922
TextMessage textMessage => textMessage.FormatMessage(),
2023
ImageMessage imageMessage => imageMessage.FormatMessage(),
2124
ToolCallMessage toolCallMessage => toolCallMessage.FormatMessage(),
@@ -110,6 +113,8 @@ public static string FormatMessage(this AggregateMessage<ToolCallMessage, ToolCa
110113

111114
return sb.ToString();
112115
}
116+
117+
[Obsolete("This method is deprecated, please use the extension method FormatMessage(this IMessage message) instead.")]
113118
public static string FormatMessage(this Message message)
114119
{
115120
var sb = new StringBuilder();
@@ -149,15 +154,16 @@ public static bool IsSystemMessage(this IMessage message)
149154
return message switch
150155
{
151156
TextMessage textMessage => textMessage.Role == Role.System,
157+
#pragma warning disable CS0618 // deprecated
152158
Message msg => msg.Role == Role.System,
159+
#pragma warning restore CS0618 // deprecated
153160
_ => false,
154161
};
155162
}
156163

157164
/// <summary>
158165
/// Get the content from the message
159-
/// <para>if the message is a <see cref="Message"/> or <see cref="TextMessage"/>, return the content</para>
160-
/// <para>if the message is a <see cref="ToolCallResultMessage"/> and only contains one function call, return the result of that function call</para>
166+
/// <para>if the message implements <see cref="ICanGetTextContent"/>, return the content from the message by calling <see cref="ICanGetTextContent.GetContent()"/></para>
161167
/// <para>if the message is a <see cref="AggregateMessage{ToolCallMessage, ToolCallResultMessage}"/> where TMessage1 is <see cref="ToolCallMessage"/> and TMessage2 is <see cref="ToolCallResultMessage"/> and the second message only contains one function call, return the result of that function call</para>
162168
/// <para>for all other situation, return null.</para>
163169
/// </summary>
@@ -166,10 +172,11 @@ public static bool IsSystemMessage(this IMessage message)
166172
{
167173
return message switch
168174
{
169-
TextMessage textMessage => textMessage.Content,
170-
Message msg => msg.Content,
171-
ToolCallResultMessage toolCallResultMessage => toolCallResultMessage.ToolCalls.Count == 1 ? toolCallResultMessage.ToolCalls.First().Result : null,
175+
ICanGetTextContent canGetTextContent => canGetTextContent.GetContent(),
172176
AggregateMessage<ToolCallMessage, ToolCallResultMessage> aggregateMessage => string.Join("\n", aggregateMessage.Message2.ToolCalls.Where(x => x.Result is not null).Select(x => x.Result)),
177+
#pragma warning disable CS0618 // deprecated
178+
Message msg => msg.Content,
179+
#pragma warning restore CS0618 // deprecated
173180
_ => null,
174181
};
175182
}
@@ -182,7 +189,9 @@ public static bool IsSystemMessage(this IMessage message)
182189
return message switch
183190
{
184191
TextMessage textMessage => textMessage.Role,
192+
#pragma warning disable CS0618 // deprecated
185193
Message msg => msg.Role,
194+
#pragma warning restore CS0618 // deprecated
186195
ImageMessage img => img.Role,
187196
MultiModalMessage multiModal => multiModal.Role,
188197
_ => null,
@@ -191,8 +200,7 @@ public static bool IsSystemMessage(this IMessage message)
191200

192201
/// <summary>
193202
/// Return the tool calls from the message if it's available.
194-
/// <para>if the message is a <see cref="ToolCallMessage"/>, return its tool calls</para>
195-
/// <para>if the message is a <see cref="Message"/> and the function name and function arguments are available, return a list of tool call with one item</para>
203+
/// <para>if the message implements <see cref="ICanGetToolCalls"/>, return the tool calls from the message by calling <see cref="ICanGetToolCalls.GetToolCalls()"/></para>
196204
/// <para>if the message is a <see cref="AggregateMessage{ToolCallMessage, ToolCallResultMessage}"/> where TMessage1 is <see cref="ToolCallMessage"/> and TMessage2 is <see cref="ToolCallResultMessage"/>, return the tool calls from the first message</para>
197205
/// </summary>
198206
/// <param name="message"></param>
@@ -201,11 +209,13 @@ public static bool IsSystemMessage(this IMessage message)
201209
{
202210
return message switch
203211
{
204-
ToolCallMessage toolCallMessage => toolCallMessage.ToolCalls,
212+
ICanGetToolCalls canGetToolCalls => canGetToolCalls.GetToolCalls().ToList(),
213+
#pragma warning disable CS0618 // deprecated
205214
Message msg => msg.FunctionName is not null && msg.FunctionArguments is not null
206-
? msg.Content is not null ? new List<ToolCall> { new ToolCall(msg.FunctionName, msg.FunctionArguments, result: msg.Content) }
207-
: new List<ToolCall> { new ToolCall(msg.FunctionName, msg.FunctionArguments) }
215+
? msg.Content is not null ? [new ToolCall(msg.FunctionName, msg.FunctionArguments, result: msg.Content)]
216+
: new List<ToolCall> { new(msg.FunctionName, msg.FunctionArguments) }
208217
: null,
218+
#pragma warning restore CS0618 // deprecated
209219
AggregateMessage<ToolCallMessage, ToolCallResultMessage> aggregateMessage => aggregateMessage.Message1.ToolCalls,
210220
_ => null,
211221
};

dotnet/src/AutoGen.Core/GroupChat/GroupChat.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public async Task<IAgent> SelectNextSpeakerAsync(IAgent currentSpeaker, IEnumera
110110
{string.Join(",", agentNames)}
111111
112112
Each message will start with 'From name:', e.g:
113-
From admin:
113+
From {agentNames.First()}:
114114
//your message//.");
115115

116116
var conv = this.ProcessConversationsForRolePlay(this.initializeMessages, conversationHistory);

dotnet/src/AutoGen.Core/Message/IMessage.cs

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// IMessage.cs
33

4+
using System.Collections.Generic;
5+
46
namespace AutoGen.Core;
57

68
/// <summary>
@@ -29,7 +31,7 @@ namespace AutoGen.Core;
2931
/// <item>
3032
/// <see cref="AggregateMessage{TMessage1, TMessage2}"/>: an aggregate message type that contains two message types.
3133
/// This type is useful when you want to combine two message types into one unique message type. One example is when invoking a tool call and you want to return both <see cref="ToolCallMessage"/> and <see cref="ToolCallResultMessage"/>.
32-
/// One example of how this type is used in AutoGen is <see cref="FunctionCallMiddleware"/>
34+
/// One example of how this type is used in AutoGen is <see cref="FunctionCallMiddleware"/> and its return message <see cref="ToolCallAggregateMessage"/>
3335
/// </item>
3436
/// </list>
3537
/// </summary>
@@ -41,6 +43,24 @@ public interface IMessage<out T> : IMessage, IStreamingMessage<T>
4143
{
4244
}
4345

46+
/// <summary>
47+
/// The interface for messages that can get text content.
48+
/// This interface will be used by <see cref="MessageExtension.GetContent(IMessage)"/> to get the content from the message.
49+
/// </summary>
50+
public interface ICanGetTextContent : IMessage, IStreamingMessage
51+
{
52+
public string? GetContent();
53+
}
54+
55+
/// <summary>
56+
/// The interface for messages that can get a list of <see cref="ToolCall"/>
57+
/// </summary>
58+
public interface ICanGetToolCalls : IMessage, IStreamingMessage
59+
{
60+
public IEnumerable<ToolCall> GetToolCalls();
61+
}
62+
63+
4464
public interface IStreamingMessage
4565
{
4666
string? From { get; set; }

dotnet/src/AutoGen.Core/Message/Message.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Message.cs
33

4+
using System;
45
using System.Collections.Generic;
56

67
namespace AutoGen.Core;
78

9+
[Obsolete("This message class is deprecated, please use a specific AutoGen built-in message type instead. For more information, please visit https://microsoft.github.io/autogen-for-net/articles/Built-in-messages.html")]
810
public class Message : IMessage
911
{
1012
public Message(

dotnet/src/AutoGen.Core/Message/TextMessage.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace AutoGen.Core;
55

6-
public class TextMessage : IMessage, IStreamingMessage
6+
public class TextMessage : IMessage, IStreamingMessage, ICanGetTextContent
77
{
88
public TextMessage(Role role, string content, string? from = null)
99
{
@@ -44,9 +44,14 @@ public override string ToString()
4444
{
4545
return $"TextMessage({this.Role}, {this.Content}, {this.From})";
4646
}
47+
48+
public string? GetContent()
49+
{
50+
return this.Content;
51+
}
4752
}
4853

49-
public class TextMessageUpdate : IStreamingMessage
54+
public class TextMessageUpdate : IStreamingMessage, ICanGetTextContent
5055
{
5156
public TextMessageUpdate(Role role, string? content, string? from = null)
5257
{
@@ -60,4 +65,9 @@ public TextMessageUpdate(Role role, string? content, string? from = null)
6065
public string? From { get; set; }
6166

6267
public Role Role { get; set; }
68+
69+
public string? GetContent()
70+
{
71+
return this.Content;
72+
}
6373
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// FunctionCallAggregateMessage.cs
3+
4+
using System.Collections.Generic;
5+
6+
namespace AutoGen.Core;
7+
8+
/// <summary>
9+
/// An aggregate message that contains a tool call message and a tool call result message.
10+
/// This message type is used by <see cref="FunctionCallMiddleware"/> to return both <see cref="ToolCallMessage"/> and <see cref="ToolCallResultMessage"/>.
11+
/// </summary>
12+
public class ToolCallAggregateMessage : AggregateMessage<ToolCallMessage, ToolCallResultMessage>, ICanGetTextContent, ICanGetToolCalls
13+
{
14+
public ToolCallAggregateMessage(ToolCallMessage message1, ToolCallResultMessage message2, string? from = null)
15+
: base(message1, message2, from)
16+
{
17+
}
18+
19+
public string? GetContent()
20+
{
21+
return this.Message2.GetContent();
22+
}
23+
24+
public IEnumerable<ToolCall> GetToolCalls()
25+
{
26+
return this.Message1.GetToolCalls();
27+
}
28+
}

0 commit comments

Comments
 (0)