Skip to content

Commit

Permalink
Merge pull request #1821 from microsoft/u/xiaoyun/refactorMiddleware
Browse files Browse the repository at this point in the history
[.Net] Add MiddlewareAgent<TAgent> and MiddlewareStreamingAgent<TAgent>
  • Loading branch information
LittleLittleCloud committed Feb 29, 2024
2 parents c28b0cc + 077cb46 commit 25498f6
Show file tree
Hide file tree
Showing 13 changed files with 230 additions and 24 deletions.
27 changes: 25 additions & 2 deletions dotnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,30 @@ You can find more examples under the [sample project](https://github.com/microso
- [x] Source generator for type-safe function definition generation

#### Update log
##### 2024-01-04
##### Update on 0.0.8 (2024-02-28)
- Fix [#1804](https://github.com/microsoft/autogen/pull/1804)
- Streaming support for IAgent [#1656](https://github.com/microsoft/autogen/pull/1656)
- Streaming support for middleware via `MiddlewareStreamingAgent` [#1656](https://github.com/microsoft/autogen/pull/1656)
- Graph chat support with conditional transition workflow [#1761](https://github.com/microsoft/autogen/pull/1761)
- AutoGen.SourceGenerator: Generate `FunctionContract` from `FunctionAttribute` [#1736](https://github.com/microsoft/autogen/pull/1736)
##### Update on 0.0.7 (2024-02-11)
- Add `AutoGen.LMStudio` to support comsume openai-like API from LMStudio local server
##### Update on 0.0.6 (2024-01-23)
- Add `MiddlewareAgent`
- Use `MiddlewareAgent` to implement existing agent hooks (RegisterPreProcess, RegisterPostProcess, RegisterReply)
- Remove `AutoReplyAgent`, `PreProcessAgent`, `PostProcessAgent` because they are replaced by `MiddlewareAgent`
##### Update on 0.0.5
- Simplify `IAgent` interface by removing `ChatLLM` Property
- Add `GenerateReplyOptions` to `IAgent.GenerateReplyAsync` which allows user to specify or override the options when generating reply

##### Update on 0.0.4
- Move out dependency of Semantic Kernel
- Add type `IChatLLM` as connector to LLM

##### Update on 0.0.3
- In AutoGen.SourceGenerator, rename FunctionAttribution to FunctionAttribute
- In AutoGen, refactor over ConversationAgent, UserProxyAgent, and AssistantAgent

##### Update on 0.0.2
- update Azure.OpenAI.AI to 1.0.0-beta.12
- update Semantic kernel to 1.0.1
- add support and example for GPT-4-Vision-Preview
2 changes: 1 addition & 1 deletion dotnet/eng/MetaInfo.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VersionPrefix>0.0.7</VersionPrefix>
<VersionPrefix>0.0.8</VersionPrefix>
<Authors>AutoGen</Authors>
<PackageProjectUrl>https://github.com/microsoft/autogen</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/autogen</RepositoryUrl>
Expand Down
10 changes: 8 additions & 2 deletions dotnet/nuget/NUGET.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
## AutoGen
### Update on 0.0.7 (2024-02-11)
#### Update on 0.0.8 (2024-02-28)
- Fix [#1804](https://github.com/microsoft/autogen/pull/1804)
- Streaming support for IAgent [#1656](https://github.com/microsoft/autogen/pull/1656)
- Streaming support for middleware via `MiddlewareStreamingAgent` [#1656](https://github.com/microsoft/autogen/pull/1656)
- Graph chat support with conditional transition workflow [#1761](https://github.com/microsoft/autogen/pull/1761)
- AutoGen.SourceGenerator: Generate `FunctionContract` from `FunctionAttribute` [#1736](https://github.com/microsoft/autogen/pull/1736)
#### Update on 0.0.7 (2024-02-11)
- Add `AutoGen.LMStudio` to support comsume openai-like API from LMStudio local server
### Update on 0.0.6 (2024-01-23)
#### Update on 0.0.6 (2024-01-23)
- Add `MiddlewareAgent`
- Use `MiddlewareAgent` to implement existing agent hooks (RegisterPreProcess, RegisterPostProcess, RegisterReply)
- Remove `AutoReplyAgent`, `PreProcessAgent`, `PostProcessAgent` because they are replaced by `MiddlewareAgent`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public async Task CodeSnippet1()
reply.Content.Should().Be("[middleware shortcut]");
#endregion code_snippet_4

#region retrieve_inner_agent
var innerAgent = middlewareAgent.Agent;
#endregion retrieve_inner_agent

#region code_snippet_logging_to_console
var agentWithLogging = middlewareAgent.RegisterMiddleware(async (messages, options, agent, ct) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static class AgentExtension
/// <param name="agent"></param>
/// <example>
/// <![CDATA[
/// [!code-csharp[Dynamic_GroupChat_Get_MLNET_PR](~/../sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Get_MLNET_PR.cs)]
/// [!code-csharp[Example04_Dynamic_GroupChat_Coding_Task](~/../sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs)]
/// ]]>
/// </example>
public static IAgent RegisterDotnetCodeBlockExectionHook(
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion dotnet/src/AutoGen/Core/Agent/ConversableAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public class ConversableAgent : IAgent
}
else
{
agent = new MiddlewareAgent(new DefaultReplyAgent(this.Name!, this.defaultReply ?? "Default reply is not set. Please pass a default reply to assistant agent"));
agent = new MiddlewareAgent<DefaultReplyAgent>(new DefaultReplyAgent(this.Name!, this.defaultReply ?? "Default reply is not set. Please pass a default reply to assistant agent"));
}

// process human input
Expand Down
47 changes: 44 additions & 3 deletions dotnet/src/AutoGen/Core/Agent/MiddlewareAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace AutoGen;
/// </summary>
public class MiddlewareAgent : IAgent
{
private readonly IAgent innerAgent;
private readonly IAgent _agent;
private readonly List<IMiddleware> middlewares = new();

/// <summary>
Expand All @@ -24,18 +24,38 @@ public class MiddlewareAgent : IAgent
/// <param name="name">the name of the agent if provided. Otherwise, the name of <paramref name="innerAgent"/> will be used.</param>
public MiddlewareAgent(IAgent innerAgent, string? name = null)
{
this.innerAgent = innerAgent;
this.Name = name ?? innerAgent.Name;
this._agent = innerAgent;
}

/// <summary>
/// Create a new instance of <see cref="MiddlewareAgent"/> by copying the middlewares from another <see cref="MiddlewareAgent"/>.
/// </summary>
public MiddlewareAgent(MiddlewareAgent other)
{
this.Name = other.Name;
this._agent = other._agent;
this.middlewares.AddRange(other.middlewares);
}

public string? Name { get; }

/// <summary>
/// Get the inner agent.
/// </summary>
public IAgent Agent => this._agent;

/// <summary>
/// Get the middlewares.
/// </summary>
public IEnumerable<IMiddleware> Middlewares => this.middlewares;

public Task<Message> GenerateReplyAsync(
IEnumerable<Message> messages,
GenerateReplyOptions? options = null,
CancellationToken cancellationToken = default)
{
var agent = this.innerAgent;
IAgent agent = this._agent;
foreach (var middleware in this.middlewares)
{
agent = new DelegateAgent(middleware, agent);
Expand Down Expand Up @@ -90,3 +110,24 @@ public DelegateAgent(IMiddleware middleware, IAgent innerAgent)
}
}
}

public sealed class MiddlewareAgent<T> : MiddlewareAgent
where T : IAgent
{
public MiddlewareAgent(T innerAgent, string? name = null)
: base(innerAgent, name)
{
this.TAgent = innerAgent;
}

public MiddlewareAgent(MiddlewareAgent<T> other)
: base(other)
{
this.TAgent = other.TAgent;
}

/// <summary>
/// Get the inner agent of type <typeparamref name="T"/>.
/// </summary>
public T TAgent { get; }
}
31 changes: 31 additions & 0 deletions dotnet/src/AutoGen/Core/Agent/MiddlewareStreamingAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ public MiddlewareStreamingAgent(IStreamingAgent agent, string? name = null, IEnu

public string? Name { get; }

/// <summary>
/// Get the inner agent.
/// </summary>
public IStreamingAgent Agent => _agent;

/// <summary>
/// Get the middlewares.
/// </summary>
public IEnumerable<IStreamingMiddleware> Middlewares => _middlewares;

public async Task<Message> GenerateReplyAsync(IEnumerable<Message> messages, GenerateReplyOptions? options = null, CancellationToken cancellationToken = default)
{
var reply = await GenerateStreamingReplyAsync(messages, options, cancellationToken);
Expand Down Expand Up @@ -93,3 +103,24 @@ public Task<IAsyncEnumerable<Message>> GenerateStreamingReplyAsync(IEnumerable<M
}
}
}

public sealed class MiddlewareStreamingAgent<T> : MiddlewareStreamingAgent
where T : IStreamingAgent
{
public MiddlewareStreamingAgent(T innerAgent, string? name = null)
: base(innerAgent, name)
{
TAgent = innerAgent;
}

public MiddlewareStreamingAgent(MiddlewareStreamingAgent<T> other)
: base(other)
{
TAgent = other.TAgent;
}

/// <summary>
/// Get the inner agent.
/// </summary>
public T TAgent { get; }
}
120 changes: 107 additions & 13 deletions dotnet/src/AutoGen/Core/Extension/MiddlewareExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ public static class MiddlewareExtension
/// <param name="replyFunc"></param>
/// <returns></returns>
/// <exception cref="Exception">throw when agent name is null.</exception>
public static IAgent RegisterReply(
this IAgent agent,
public static MiddlewareAgent<TAgent> RegisterReply<TAgent>(
this TAgent agent,
Func<IEnumerable<Message>, CancellationToken, Task<Message?>> replyFunc)
where TAgent : IAgent
{
return agent.RegisterMiddleware(async (messages, options, agent, ct) =>
{
Expand All @@ -41,10 +42,11 @@ public static class MiddlewareExtension
/// <summary>
/// Print formatted message to console.
/// </summary>
public static MiddlewareAgent RegisterPrintFormatMessageHook(this IAgent agent)
public static MiddlewareAgent<TAgent> RegisterPrintFormatMessageHook<TAgent>(this TAgent agent)
where TAgent : IAgent
{
var middleware = new PrintMessageMiddleware();
var middlewareAgent = new MiddlewareAgent(agent);
var middlewareAgent = new MiddlewareAgent<TAgent>(agent);
middlewareAgent.Use(middleware);

return middlewareAgent;
Expand All @@ -54,12 +56,13 @@ public static MiddlewareAgent RegisterPrintFormatMessageHook(this IAgent agent)
/// Register a post process hook to an agent. The hook will be called before the agent return the reply and after the agent generate the reply.
/// This is useful when you want to customize arbitrary behavior before the agent return the reply.
///
/// One example is <see cref="RegisterPrintFormatMessageHook(IAgent)"/>, which print the formatted message to console before the agent return the reply.
/// One example is <see cref="RegisterPrintFormatMessageHook{TAgent}(TAgent)"/>, which print the formatted message to console before the agent return the reply.
/// </summary>
/// <exception cref="Exception">throw when agent name is null.</exception>
public static MiddlewareAgent RegisterPostProcess(
this IAgent agent,
public static MiddlewareAgent<TAgent> RegisterPostProcess<TAgent>(
this TAgent agent,
Func<IEnumerable<Message>, Message, CancellationToken, Task<Message>> postprocessFunc)
where TAgent : IAgent
{
return agent.RegisterMiddleware(async (messages, options, agent, ct) =>
{
Expand Down Expand Up @@ -88,17 +91,19 @@ public static MiddlewareAgent RegisterPrintFormatMessageHook(this IAgent agent)
/// <summary>
/// Register a middleware to an existing agent and return a new agent with the middleware.
/// </summary>
public static MiddlewareAgent RegisterMiddleware(
this IAgent agent,
public static MiddlewareAgent<TAgent> RegisterMiddleware<TAgent>(
this TAgent agent,
Func<IEnumerable<Message>, GenerateReplyOptions?, IAgent, CancellationToken, Task<Message>> func,
string? middlewareName = null)
where TAgent : IAgent
{
if (agent.Name == null)
{
throw new Exception("Agent name is null.");
}

var middlewareAgent = new MiddlewareAgent(agent);

var middlewareAgent = new MiddlewareAgent<TAgent>(agent);
middlewareAgent.Use(func, middlewareName);

return middlewareAgent;
Expand All @@ -107,18 +112,107 @@ public static MiddlewareAgent RegisterPrintFormatMessageHook(this IAgent agent)
/// <summary>
/// Register a middleware to an existing agent and return a new agent with the middleware.
/// </summary>
public static MiddlewareAgent RegisterMiddleware(
this IAgent agent,
public static MiddlewareAgent<TAgent> RegisterMiddleware<TAgent>(
this TAgent agent,
IMiddleware middleware)
where TAgent : IAgent
{
if (agent.Name == null)
{
throw new Exception("Agent name is null.");
}

var middlewareAgent = new MiddlewareAgent(agent);
var middlewareAgent = new MiddlewareAgent<TAgent>(agent);
middlewareAgent.Use(middleware);

return middlewareAgent;
}

/// <summary>
/// Register a middleware to an existing agent and return a new agent with the middleware.
/// </summary>
public static MiddlewareAgent<TAgent> RegisterMiddleware<TAgent>(
this MiddlewareAgent<TAgent> agent,
Func<IEnumerable<Message>, GenerateReplyOptions?, IAgent, CancellationToken, Task<Message>> func,
string? middlewareName = null)
where TAgent : IAgent
{
var copyAgent = new MiddlewareAgent<TAgent>(agent);
copyAgent.Use(func, middlewareName);

return copyAgent;
}

/// <summary>
/// Register a middleware to an existing agent and return a new agent with the middleware.
/// </summary>
public static MiddlewareAgent<TAgent> RegisterMiddleware<TAgent>(
this MiddlewareAgent<TAgent> agent,
IMiddleware middleware)
where TAgent : IAgent
{
var copyAgent = new MiddlewareAgent<TAgent>(agent);
copyAgent.Use(middleware);

return copyAgent;
}

/// <summary>
/// Register a middleware to an existing agent and return a new agent with the middleware.
/// </summary>
public static MiddlewareStreamingAgent<TAgent> RegisterMiddleware<TAgent>(
this TAgent agent,
IStreamingMiddleware middleware)
where TAgent : IStreamingAgent
{
var middlewareAgent = new MiddlewareStreamingAgent<TAgent>(agent);
middlewareAgent.Use(middleware);

return middlewareAgent;
}

/// <summary>
/// Register a middleware to an existing agent and return a new agent with the middleware.
/// </summary>
public static MiddlewareStreamingAgent<TAgent> RegisterMiddleware<TAgent>(
this MiddlewareStreamingAgent<TAgent> agent,
IStreamingMiddleware middleware)
where TAgent : IStreamingAgent
{
var copyAgent = new MiddlewareStreamingAgent<TAgent>(agent);
copyAgent.Use(middleware);

return copyAgent;
}


/// <summary>
/// Register a middleware to an existing agent and return a new agent with the middleware.
/// </summary>
public static MiddlewareStreamingAgent<TAgent> RegisterMiddleware<TAgent>(
this TAgent agent,
Func<MiddlewareContext, IStreamingAgent, CancellationToken, Task<IAsyncEnumerable<Message>>> func,
string? middlewareName = null)
where TAgent : IStreamingAgent
{
var middlewareAgent = new MiddlewareStreamingAgent<TAgent>(agent);
middlewareAgent.Use(func, middlewareName);

return middlewareAgent;
}

/// <summary>
/// Register a middleware to an existing agent and return a new agent with the middleware.
/// </summary>
public static MiddlewareStreamingAgent<TAgent> RegisterMiddleware<TAgent>(
this MiddlewareStreamingAgent<TAgent> agent,
Func<MiddlewareContext, IStreamingAgent, CancellationToken, Task<IAsyncEnumerable<Message>>> func,
string? middlewareName = null)
where TAgent : IStreamingAgent
{
var copyAgent = new MiddlewareStreamingAgent<TAgent>(agent);
copyAgent.Use(func, middlewareName);

return copyAgent;
}
}
2 changes: 1 addition & 1 deletion dotnet/src/AutoGen/Core/Function/FunctionAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class FunctionContract

/// <summary>
/// The description of the return section.
/// If a structured comment is available, the description will be extracted from the <returns /> section.
/// If a structured comment is available, the description will be extracted from the return section.
/// Otherwise, the description will be null.
/// </summary>
public string? ReturnDescription { get; set; }
Expand Down
2 changes: 2 additions & 0 deletions dotnet/test/AutoGen.Tests/MiddlewareAgentTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,7 @@ public async Task RegisterMiddlewareTestAsync()

reply = await middlewareAgent.SendAsync("hello");
reply.Content.Should().Be("[middleware shortcut] hello");

middlewareAgent.Middlewares.Count().Should().Be(3);
}
}
Loading

0 comments on commit 25498f6

Please sign in to comment.