Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.NET AgentChat #4561

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions dotnet/AutoGen.sln
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AutoGen.Agents", "src\Microsoft.AutoGen\Agents\Microsoft.AutoGen.Agents.csproj", "{FD87BD33-4616-460B-AC85-A412BA08BB78}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AutoGen.AgentChat", "src\Microsoft.AutoGen\AgentChat\Microsoft.AutoGen.AgentChat.csproj", "{94D45CDD-4D33-40CC-AD00-57785DA84997}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AutoGen.Abstractions", "src\Microsoft.AutoGen\Abstractions\Microsoft.AutoGen.Abstractions.csproj", "{E0C991D9-0DB8-471C-ADC9-5FB16E2A0106}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AutoGen.Extensions.SemanticKernel", "src\Microsoft.AutoGen\Extensions\SemanticKernel\Microsoft.AutoGen.Extensions.SemanticKernel.csproj", "{952827D4-8D4C-4327-AE4D-E8D25811EF35}"
Expand Down Expand Up @@ -112,8 +114,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.Backend", "samples\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.Shared", "samples\dev-team\DevTeam.Shared\DevTeam.Shared.csproj", "{01F5D7C3-41EB-409C-9B77-A945C07FA7E8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hello", "Hello", "{7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend", "samples\Hello\Backend\Backend.csproj", "{C428C6E5-B0E5-4DA6-B0F7-43013D2ECE69}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hello.AppHost", "samples\Hello\Hello.AppHost\Hello.AppHost.csproj", "{09A373A0-8169-409F-8C37-3FBC1654B122}"
Expand Down Expand Up @@ -256,6 +256,10 @@ Global
{FD87BD33-4616-460B-AC85-A412BA08BB78}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD87BD33-4616-460B-AC85-A412BA08BB78}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD87BD33-4616-460B-AC85-A412BA08BB78}.Release|Any CPU.Build.0 = Release|Any CPU
{94D45CDD-4D33-40CC-AD00-57785DA84997}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94D45CDD-4D33-40CC-AD00-57785DA84997}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94D45CDD-4D33-40CC-AD00-57785DA84997}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94D45CDD-4D33-40CC-AD00-57785DA84997}.Release|Any CPU.Build.0 = Release|Any CPU
{E0C991D9-0DB8-471C-ADC9-5FB16E2A0106}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E0C991D9-0DB8-471C-ADC9-5FB16E2A0106}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0C991D9-0DB8-471C-ADC9-5FB16E2A0106}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -383,6 +387,7 @@ Global
{42A8251C-E7B3-47BB-A82E-459952EBE132} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{4BB66E06-37D8-45A0-9B97-DE590AFBA340} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{FD87BD33-4616-460B-AC85-A412BA08BB78} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{94D45CDD-4D33-40CC-AD00-57785DA84997} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{E0C991D9-0DB8-471C-ADC9-5FB16E2A0106} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{952827D4-8D4C-4327-AE4D-E8D25811EF35} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{668726B9-77BC-45CF-B576-0F0773BF1615} = {686480D7-8FEC-4ED3-9C5D-CEBE1057A7ED}
Expand All @@ -399,11 +404,6 @@ Global
{63280C12-3BE3-4C4E-805E-584CDC6BC1F5} = {05B9C173-6441-4DCA-9AC4-E897EF75F331}
{EDA3EF83-FC7F-4BCF-945D-B893620EE4B1} = {05B9C173-6441-4DCA-9AC4-E897EF75F331}
{01F5D7C3-41EB-409C-9B77-A945C07FA7E8} = {05B9C173-6441-4DCA-9AC4-E897EF75F331}
{7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45} = {686480D7-8FEC-4ED3-9C5D-CEBE1057A7ED}
{C428C6E5-B0E5-4DA6-B0F7-43013D2ECE69} = {7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}
{09A373A0-8169-409F-8C37-3FBC1654B122} = {7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}
{A20B9894-F352-4338-872A-F215A241D43D} = {7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}
{8F7560CF-EEBB-4333-A69F-838CA40FD85D} = {7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}
{97550E87-48C6-4EBF-85E1-413ABAE9DBFD} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{64EF61E7-00A6-4E5E-9808-62E10993A0E5} = {7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}
{65059914-5527-4A00-9308-9FAF23D5E85A} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
Expand Down
23 changes: 23 additions & 0 deletions dotnet/src/Microsoft.AutoGen/Abstractions/ICodeExecutor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ICodeExecutor.cs

namespace Microsoft.AutoGen.Abstractions;

// TODO: Should these be classes?
public struct CodeBlock
{
public required string Code { get; set; }
public required string Language { get; set; } // TODO: We should raise this into the routing type, somehow
}

public struct CodeResult
{
public required int ExitCode { get; set; }
public required string Output { get; set; }
}

public interface ICodeExecutor
{
ValueTask<CodeResult> ExecuteCodeBlocksAsync(IEnumerable<CodeBlock> codeBlocks, CancellationToken cancellationToken = default);
ValueTask RestartAsync();
}
42 changes: 41 additions & 1 deletion dotnet/src/Microsoft.AutoGen/Abstractions/IHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,47 @@ public interface IHandle
Task HandleObject(object item);
}

public interface IHandle<T> : IHandle
public interface IHandle<in T> : IHandle
{
// TODO: Should this be a ValueTask?
Task Handle(T item);
}

public interface IHandleEx<in TIn> : IHandle<TIn>
{
Task IHandle<TIn>.Handle(TIn item)
{
return this.HandleAsync(item, CancellationToken.None).AsTask();
}

ValueTask HandleAsync(TIn item)
{
return this.HandleAsync(item, CancellationToken.None);
}

ValueTask HandleAsync(TIn item, CancellationToken cancellationToken);
}

public interface IHandleEx<in TIn, TOut> // TODO: Map this to IHandle<> somehow?
{
ValueTask<TOut> HandleAsync(TIn item)
{
return this.HandleAsync(item, CancellationToken.None);
}

ValueTask<TOut> HandleAsync(TIn item, CancellationToken cancellationToken);
}

public interface IHandleDefault : IHandleEx<object>
{
}

public interface IHandleStream<in TIn, TOut>
{
IAsyncEnumerable<TOut> StreamAsync(TIn item)
{
return this.StreamAsync(item, CancellationToken.None);
}

IAsyncEnumerable<TOut> StreamAsync(TIn item, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Protobuf Include="..\..\..\..\protos\agent_worker.proto" GrpcServices="Client;Server" Link="Protos\agent_worker.proto" />
<Protobuf Include="..\..\..\..\protos\cloudevent.proto" GrpcServices="Client;Server" Link="Protos\cloudevent.proto" />
<Protobuf Include="..\..\..\..\protos\agent_events.proto" GrpcServices="Client;Server" Link="Protos\agent_events.proto" />
<Protobuf Include="..\..\..\..\protos\agentchat_events.proto" GrpcServices="Client;Server" Link="Protos\agentchat_events.proto" />
</ItemGroup>

<ItemGroup>
Expand Down
147 changes: 147 additions & 0 deletions dotnet/src/Microsoft.AutoGen/AgentChat/Abstractions/ChatAgent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ChatAgent.cs

using System.Text.RegularExpressions;
using Microsoft.AutoGen.Abstractions;

namespace Microsoft.AutoGen.AgentChat.Abstractions;

public struct AgentName
{
// To ensure parity with Python, we require agent names to be identifiers
// TODO: Ensure that only valid C# identifiers can pass the validation on Python?

/*
From https://docs.python.org/3/reference/lexical_analysis.html#identifiers:
```
identifier ::= xid_start xid_continue*
id_start ::= <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>
id_continue ::= <all characters in id_start, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>
xid_start ::= <all characters in id_start whose NFKC normalization is in "id_start xid_continue*">
xid_continue ::= <all characters in id_continue whose NFKC normalization is in "id_continue*">
```

Note: we are not going to deal with normalization; it would require a lot of effort for likely little gain
(this will mean that, strictly speaking, .NET will support a subset of the identifiers that Python does)

The Unicode category codes mentioned above stand for:

* Lu - uppercase letters
* Ll - lowercase letters
* Lt - titlecase letters
* Lm - modifier letters
* Lo - other letters
* Nl - letter numbers*
* Mn - nonspacing marks
* Mc - spacing combining marks*
* Nd - decimal numbers
* Pc - connector punctuations

Of these, most are captured by "word characters" in .NET, \w, only needing \p{Nl} and \p{Mc} to be added.
While Copilot /thinks/ that \p{Pc} is needed, it is not, as it is part of \w in .NET.

* Other_ID_Start - explicit list of characters in PropList.txt to support backwards compatibility
* Other_ID_Continue - likewise

# ================================================

1885..1886 ; Other_ID_Start # Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA
2118 ; Other_ID_Start # Sm SCRIPT CAPITAL P
212E ; Other_ID_Start # So ESTIMATED SYMBOL
309B..309C ; Other_ID_Start # Sk [2] KATAKANA-HIRAGANA VOICED SOUND MARK..KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK

# Total code points: 6

The pattern for this in .NET is [\u1185-\u1186\u2118\u212E\u309B-\u309C]

# ================================================

00B7 ; Other_ID_Continue # Po MIDDLE DOT
0387 ; Other_ID_Continue # Po GREEK ANO TELEIA
1369..1371 ; Other_ID_Continue # No [9] ETHIOPIC DIGIT ONE..ETHIOPIC DIGIT NINE
19DA ; Other_ID_Continue # No NEW TAI LUE THAM DIGIT ONE
200C..200D ; Other_ID_Continue # Cf [2] ZERO WIDTH NON-JOINER..ZERO WIDTH JOINER
30FB ; Other_ID_Continue # Po KATAKANA MIDDLE DOT
FF65 ; Other_ID_Continue # Po HALFWIDTH KATAKANA MIDDLE DOT

# Total code points: 16

The pattern for this in .NET is [\u00B7\u0387\u1369-\u1371\u19DA\u200C\u200D\u30FB\uFF65]

# ================================================

Classes for "IdStart": {Lu, Ll, Lt, Lm, Lo, Nl, '_', Other_ID_Start}
pattern: [\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}_\u1185-\u1186\u2118\u212E\u309B-\u309C]

Classes for "IdContinue": {\w, Nl, Mc, Other_ID_Start, Other_ID_Continue}
pattern: [\w\p{Nl}\p{Mc}_\u1185-\u1186\u2118\u212E\u309B-\u309C\u00B7\u0387\u1369-\u1371\u19DA\u200C\u200D\u30FB\uFF65]

Match group for identifiers:
(?<ident>(?:[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}_\u1185-\u1186\u2118\u212E\u309B-\u309C])(?:[\w\p{Nl}\p{Mc}_\u1185-\u1186\u2118\u212E\u309B-\u309C\u00B7\u0387\u1369-\u1371\u19DA\u200C\u200D\u30FB\uFF65])*)
*/

private const string IdStartClass = @"[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}_\u1185-\u1186\u2118\u212E\u309B-\u309C]";
private const string IdContinueClass = @"[\w\p{Nl}\p{Mc}_\u1185-\u1186\u2118\u212E\u309B-\u309C\u00B7\u0387\u1369-\u1371\u19DA\u200C\u200D\u30FB\uFF65]";

private static readonly Regex AgentNameRegex = new Regex($"^{IdStartClass}{IdContinueClass}*$", RegexOptions.Compiled | RegexOptions.Singleline);

public string Name { get; }

public AgentName(string name)
{
AgentName.CheckValid(name);

this.Name = name;
}

public static bool IsValid(string name) => AgentNameRegex.IsMatch(name);

public static void CheckValid(string name)
{
if (!AgentName.IsValid(name))
{
throw new ArgumentException($"Agent name '{name}' is not a valid identifier.");
}
}

// Implicit cast to string
public static implicit operator string(AgentName agentName) => agentName.Name;
}

public class Response
{
public required ChatMessage Message { get; set; }

public List<AgentMessage>? InnerMessages { get; set; }
}

public class StreamingFrame<TResponse, TInternalMessage>() where TInternalMessage : AgentMessage
{
public enum FrameType
{
InternalMessage,
Response
}

public FrameType Type { get; set; }

public TInternalMessage? InternalMessage { get; set; }
public TResponse? Response { get; set; }
}

public class StreamingFrame<TResponse> : StreamingFrame<TResponse, AgentMessage>;

public class ChatStreamFrame : StreamingFrame<Response, AgentMessage>;

public interface IChatAgent :
IHandleEx<IEnumerable<ChatMessage>, Response>,
IHandleStream<IEnumerable<ChatMessage>, ChatStreamFrame>
{
AgentName Name { get; }
string Description { get; }

IEnumerable<Type> ProducedMessageTypes { get; } // TODO: Is there a way to make this part of the type somehow?
// Annotations, or IProduce<>? Do we ever actually access this?

ValueTask ResetAsync(CancellationToken cancellationToken);
}
26 changes: 26 additions & 0 deletions dotnet/src/Microsoft.AutoGen/AgentChat/Abstractions/Handoff.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Handoff.cs

namespace Microsoft.AutoGen.AgentChat.Abstractions;

public class Handoff(string target, string? description = null, string? name = null, string? message = null)
{
private static string? CheckName(string? name)
{
if (name != null && !AgentName.IsValid(name))
{
throw new ArgumentException($"Handoff name '{name}' is not a valid identifier.");
}

return name;
}

public AgentName Target { get; } = new AgentName(target);
public string Description { get; } = description ?? $"Handoff to {target}";
public string Name { get; } = CheckName(name) ?? $"transfer_to_{target.ToLowerInvariant()}";
public string Message { get; } = message ?? $"Transferred to {target}, adopting the role of {target} immediately.";

private string DoHandoff() => this.Message;

public ITool HandoffTool => new CallableTool(this.Name, this.Description, this.DoHandoff);
}
9 changes: 9 additions & 0 deletions dotnet/src/Microsoft.AutoGen/AgentChat/Abstractions/ITeam.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ITeam.cs

namespace Microsoft.AutoGen.AgentChat.Abstractions;

public interface ITeam : ITaskRunner
{
ValueTask ResetAsync(CancellationToken cancellationToken = default);
}
Loading
Loading