.NET: Add AgentWorkflowBuilder group chat (#861)

* Add AgentWorkflowBuilder group chat

And fix a variety of issues along the way:
- Use DateTime{Offset}.UtcNow rather than Now
- AIAgentHostExecutor shouldn't be publishing empty messages
- Sequential workflows should be flowing all history and not just the output from the previous agent as the input into the next agent
- Renamed some of the new agent workflow methods... still not super happy with the shape, though
- Simplified handoffs builder, e.g. using a hashset with a custom comparer instead of a dictionary
- Improved multi-service use by trying to change assistant->user role for messages created by other agents
- Changed MessageMerger to rely on M.E.AI's coalescing more and to avoid empty contents / text
- Ensured that messages from ChatClientAgent include MessageId and CreatedAt timestamps
- Avoided including instructions for agents in a handoff workflow that don't have any handoffs
- Removed the unnecessary end function in handoffs
- Improved naming of executors to include agent name for debuggability
- Use "N" formatting with Guid.ToString everywhere, to avoid the unnecessary extra dash character which is also not valid in various places (like function tool names)
- Replace `params T[]` with `params IEnumerable<T>` to make public APIs more flexible in what they consume

* Address feedback

- Fix unintentional provider change in sample
This commit is contained in:
Stephen Toub
2025-09-24 12:38:34 -04:00
committed by GitHub
Unverified
parent 7c70c33157
commit 03ef7f054f
51 changed files with 888 additions and 619 deletions
@@ -19,7 +19,7 @@ public abstract class InMemoryAgentThread : AgentThread
/// <param name="messageStore">An optional <see cref="InMemoryChatMessageStore"/> to use for storing chat messages. If null, a new instance will be created.</param>
protected InMemoryAgentThread(InMemoryChatMessageStore? messageStore = null)
{
this.MessageStore = messageStore ?? new InMemoryChatMessageStore();
this.MessageStore = messageStore ?? [];
}
/// <summary>
@@ -28,11 +28,7 @@ public abstract class InMemoryAgentThread : AgentThread
/// <param name="messages">The messages to initialize the thread with.</param>
protected InMemoryAgentThread(IEnumerable<ChatMessage> messages)
{
this.MessageStore = new InMemoryChatMessageStore();
foreach (var message in messages)
{
this.MessageStore.Add(message);
}
this.MessageStore = [.. messages];
}
/// <summary>
@@ -53,8 +49,7 @@ public abstract class InMemoryAgentThread : AgentThread
throw new ArgumentException("The serialized thread state must be a JSON object.", nameof(serializedThreadState));
}
var state = JsonSerializer.Deserialize(
serializedThreadState,
var state = serializedThreadState.Deserialize(
AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(InMemoryAgentThreadState))) as InMemoryAgentThreadState;
this.MessageStore =