diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index ab7a07464c52..e16f064b87e8 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -118,6 +118,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hello", "Hello", "{F42F9C8E EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Core.Grpc", "src\Microsoft.AutoGen\Core.Grpc\Microsoft.AutoGen.Core.Grpc.csproj", "{3D83C6DB-ACEA-48F3-959F-145CCD2EE135}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Agents", "src\Microsoft.AutoGen\Agents\Microsoft.AutoGen.Agents.csproj", "{FD2CD462-5B90-4547-B576-D5F10F63A4FC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -296,6 +298,12 @@ Global {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Release|Any CPU.Build.0 = Release|Any CPU + {FD2CD462-5B90-4547-B576-D5F10F63A4FC}.CoreOnly|Any CPU.ActiveCfg = Debug|Any CPU + {FD2CD462-5B90-4547-B576-D5F10F63A4FC}.CoreOnly|Any CPU.Build.0 = Debug|Any CPU + {FD2CD462-5B90-4547-B576-D5F10F63A4FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD2CD462-5B90-4547-B576-D5F10F63A4FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD2CD462-5B90-4547-B576-D5F10F63A4FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD2CD462-5B90-4547-B576-D5F10F63A4FC}.Release|Any CPU.Build.0 = Release|Any CPU {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.CoreOnly|Any CPU.ActiveCfg = Debug|Any CPU {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.CoreOnly|Any CPU.Build.0 = Debug|Any CPU {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -356,6 +364,7 @@ Global {EAFFE339-26CB-4019-991D-BCCE8E7D33A1} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {58AD8E1D-83BD-4950-A324-1A20677D78D9} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} + {FD2CD462-5B90-4547-B576-D5F10F63A4FC} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {3D83C6DB-ACEA-48F3-959F-145CCD2EE135} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {AAD593FE-A49B-425E-A9FE-A0022CD25E3D} = {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} diff --git a/dotnet/samples/Hello/HelloAgent/HelloAgent.cs b/dotnet/samples/Hello/HelloAgent/HelloAgent.cs index f188de2d0e93..113af8fc7e3a 100644 --- a/dotnet/samples/Hello/HelloAgent/HelloAgent.cs +++ b/dotnet/samples/Hello/HelloAgent/HelloAgent.cs @@ -19,7 +19,7 @@ public class HelloAgent( IHandle { // This will capture the message sent in Program.cs - public async ValueTask HandleAsync(NewMessageReceived item, MessageContext messageContext) + public async ValueTask HandleAsync(NewMessageReceived item, MessageContext messageContext, CancellationToken? cancellationToken = default) { Console.Out.WriteLine(item.Message); // Print message to console ConversationClosed goodbye = new ConversationClosed @@ -28,20 +28,19 @@ public async ValueTask HandleAsync(NewMessageReceived item, MessageContext messa UserMessage = "Goodbye" }; // This will publish the new message type which will be handled by the ConversationClosed handler - await this.PublishMessageAsync(goodbye, new TopicId("HelloTopic")); + await this.PublishMessageAsync(goodbye, new TopicId("HelloTopic"), null, cancellationToken ?? default); } - public async ValueTask HandleAsync(ConversationClosed item, MessageContext messageContext) + public async ValueTask HandleAsync(ConversationClosed item, MessageContext messageContext, CancellationToken? cancellationToken = default) { var goodbye = $"{item.UserId} said {item.UserMessage}"; // Print goodbye message to console Console.Out.WriteLine(goodbye); if (Environment.GetEnvironmentVariable("STAY_ALIVE_ON_GOODBYE") != "true") { // Publish message that will be handled by shutdown handler - await this.PublishMessageAsync(new Shutdown(), new TopicId("HelloTopic")); + await this.PublishMessageAsync(new Shutdown(), new TopicId("HelloTopic"), null, cancellationToken ?? default); } } - - public async ValueTask HandleAsync(Shutdown item, MessageContext messageContext) + public async ValueTask HandleAsync(Shutdown item, MessageContext messageContext, CancellationToken? cancellationToken = default) { Console.WriteLine("Shutting down..."); hostApplicationLifetime.StopApplication(); // Shuts down application diff --git a/dotnet/src/Microsoft.AutoGen/Agents/AIAgent/InferenceAgent.cs b/dotnet/src/Microsoft.AutoGen/Agents/AIAgent/InferenceAgent.cs new file mode 100644 index 000000000000..d3dc100012eb --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/AIAgent/InferenceAgent.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// InferenceAgent.cs +using Google.Protobuf; +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Logging; +namespace Microsoft.AutoGen.Agents; +/// +/// Base class for inference agents using the Microsoft.Extensions.AI library. +/// +/// +/// +/// +/// +/// +/// +public abstract class InferenceAgent( + AgentId id, + IAgentRuntime runtime, + string name, + ILogger>? logger, + IChatClient client) + : BaseAgent(id, runtime, name, logger) + where T : IMessage, new() +{ + protected IChatClient ChatClient { get; } = client; + private ILogger>? Logger => _logger as ILogger>; + private Task CompleteAsync( + IList chatMessages, + ChatOptions? options = null, + CancellationToken cancellationToken = default) + { + return ChatClient.CompleteAsync(chatMessages, options, cancellationToken); + } + private IAsyncEnumerable CompleteStreamingAsync( + IList chatMessages, + ChatOptions? options = null, + CancellationToken cancellationToken = default) + { + return ChatClient.CompleteStreamingAsync(chatMessages, options, cancellationToken); + } + +} diff --git a/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs new file mode 100644 index 000000000000..1c2a0c3de226 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IHandleConsole.cs +using Google.Protobuf; +using Microsoft.AutoGen.Contracts; + +namespace Microsoft.AutoGen.Agents; +/// +/// Default interface methods for an event handler for Input and Output that writes or reads from the console +/// Can be used inside your agents by inheriting from this interface +/// public class MyAgent : BaseAgent, IHandleConsole +/// +public interface IHandleConsole : IHandle, IHandle, IProcessIO +{ + /// + /// Prototype for Publish Message Async method + /// + /// + /// + /// + /// + /// + /// ValueTask + ValueTask PublishMessageAsync(T message, TopicId topic, string? messageId, CancellationToken token = default) where T : IMessage; + + /// + /// Receives events of type Output and writes them to the console + /// then runs the ProcessOutputAsync method which you should implement in your agent + /// + /// + /// + /// + /// ValueTask + async ValueTask IHandle.HandleAsync(Output item, MessageContext messageContext, CancellationToken? cancellationToken) + { + // Assuming item has a property `Message` that we want to write to the console + Console.WriteLine(item.Message); + await ProcessOutputAsync(item.Message); + + var evt = new OutputWritten + { + Route = "console" + }; + await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Receives events of type Input and reads from the console, then runs the ProcessInputAsync method + /// which you should implement in your agent + /// + /// + /// + /// + /// + async ValueTask IHandle.HandleAsync(Input item, MessageContext messageContext, CancellationToken? cancellationToken) + { + Console.WriteLine("Please enter input:"); + string content = Console.ReadLine() ?? string.Empty; + + await ProcessInputAsync(content); + + var evt = new InputProcessed + { + Route = "console" + }; + await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + } +} diff --git a/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs new file mode 100644 index 000000000000..1982929658e6 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IHandleFileIO.cs + +using Google.Protobuf; +using Microsoft.AutoGen.Contracts; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AutoGen.Agents; +/// +/// Default interface methods for an event handler for Input and Output that writes or reads from a file +/// Can be used inside your agents by inheriting from this interface +/// public class MyAgent : BaseAgent, IHandleFileIO +/// +public interface IHandleFileIO : IHandle, IHandle, IProcessIO +{ + // A Logger instance to log messages + ILogger LogTarget { get; } + // The path to the input file + string InputPath { get; } + // The path to the output file + string OutputPath { get; } + // The route of the agent (used in the post-process events) + const string Route = "Microsoft.AutoGen.Agents.IHandleFileIO"; + + /// + /// Prototype for Publish Message Async method + /// + /// + /// + /// + /// + /// + /// ValueTask + ValueTask PublishMessageAsync(T message, TopicId topic, string? messageId, CancellationToken token = default) where T : IMessage; + async ValueTask IHandle.HandleAsync(Input item, MessageContext messageContext, CancellationToken? cancellationToken) + { + + // validate that the file exists + if (!File.Exists(InputPath)) + { + var errorMessage = $"File not found: {InputPath}"; + LogTarget.LogError(errorMessage); + //publish IOError event + var err = new IOError + { + Message = errorMessage + }; + await PublishMessageAsync(err, new TopicId("IOError"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + return; + } + string content; + using (var reader = new StreamReader(item.Message)) + { + content = await reader.ReadToEndAsync(cancellationToken ?? CancellationToken.None); + } + await ProcessInputAsync(content); + var evt = new InputProcessed + { + Route = Route + }; + await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + } + async ValueTask IHandle.HandleAsync(Output item, MessageContext messageContext, CancellationToken? cancellationToken) + { + using (var writer = new StreamWriter(OutputPath, append: true)) + { + await writer.WriteLineAsync(item.Message); + } + var evt = new OutputWritten + { + Route = Route + }; + await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + } +} diff --git a/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/IProcessIO.cs b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/IProcessIO.cs new file mode 100644 index 000000000000..e348f3e1ca71 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/IProcessIO.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IProcessIO.cs + +namespace Microsoft.AutoGen.Agents; + +/// +/// Default Interface methods for processing input and output shared by IOAgents that should be implemented in your agent +/// +public interface IProcessIO +{ + /// + /// Implement this method in your agent to process the input + /// + /// + /// Task + static Task ProcessOutputAsync(string message) { return Task.CompletedTask; } + /// + /// Implement this method in your agent to process the output + /// + /// + /// Task + static Task ProcessInputAsync(string message) { return Task.FromResult(message); } +} diff --git a/dotnet/src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj b/dotnet/src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj new file mode 100644 index 000000000000..5032e95a12a0 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/protos/agent_events.proto b/dotnet/src/Microsoft.AutoGen/Agents/protos/agent_events.proto similarity index 92% rename from protos/agent_events.proto rename to dotnet/src/Microsoft.AutoGen/Agents/protos/agent_events.proto index a97df6e5855f..414d79f9678c 100644 --- a/protos/agent_events.proto +++ b/dotnet/src/Microsoft.AutoGen/Agents/protos/agent_events.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package agents; -option csharp_namespace = "Microsoft.AutoGen.Contracts"; +option csharp_namespace = "Microsoft.AutoGen.Agents"; message TextMessage { string textMessage = 1; string source = 2; diff --git a/dotnet/src/Microsoft.AutoGen/Contracts/IHandle.cs b/dotnet/src/Microsoft.AutoGen/Contracts/IHandle.cs index 422c5a247f6b..6e6c2d600253 100644 --- a/dotnet/src/Microsoft.AutoGen/Contracts/IHandle.cs +++ b/dotnet/src/Microsoft.AutoGen/Contracts/IHandle.cs @@ -14,9 +14,8 @@ public interface IHandle /// /// The item to be handled. /// A task that represents the asynchronous operation. - ValueTask HandleAsync(T item, MessageContext messageContext); + ValueTask HandleAsync(T item, MessageContext messageContext, CancellationToken? cancellationToken = default); } - public interface IHandle { /// @@ -24,5 +23,5 @@ public interface IHandle /// /// The item to be handled. /// A task that represents the asynchronous operation. - ValueTask HandleAsync(InT item, MessageContext messageContext); + ValueTask HandleAsync(InT item, MessageContext messageContext, CancellationToken? cancellationToken = default); }