Skip to content

Commit 14a3204

Browse files
LittleLittleCloudvictordibia
authored andcommitted
[.Net] Agent as service: Run an IAgent as openai chat completion endpoint (#2633)
* update * add test * clean up * update * Delete dotnet/src/AutoGen.Server/AutoGen.Service.csproj.user * implement streaming * add sample project * rename AutoGen.Service to AutoGen.WebAPI * rename AutoGen.Service to AutoGen.WebAPI
1 parent 5ef2b2d commit 14a3204

29 files changed

+889
-3
lines changed

dotnet/AutoGen.sln

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
Microsoft Visual Studio Solution File, Format Version 12.00
32
# Visual Studio Version 17
43
VisualStudioVersion = 17.8.34322.80
@@ -33,6 +32,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Mistral", "src\Auto
3332
EndProject
3433
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Mistral.Tests", "test\AutoGen.Mistral.Tests\AutoGen.Mistral.Tests.csproj", "{15441693-3659-4868-B6C1-B106F52FF3BA}"
3534
EndProject
35+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.WebAPI", "src\AutoGen.WebAPI\AutoGen.WebAPI.csproj", "{257FFD71-08E5-40C7-AB04-6A81A78EB410}"
36+
EndProject
37+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.WebAPI.Tests", "test\AutoGen.WebAPI.Tests\AutoGen.WebAPI.Tests.csproj", "{E2EF5E66-683C-4DDC-8ADA-5F676502B9BA}"
38+
EndProject
3639
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SemanticKernel.Tests", "test\AutoGen.SemanticKernel.Tests\AutoGen.SemanticKernel.Tests.csproj", "{1DFABC4A-8458-4875-8DCB-59F3802DAC65}"
3740
EndProject
3841
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.Tests", "test\AutoGen.OpenAI.Tests\AutoGen.OpenAI.Tests.csproj", "{D36A85F9-C172-487D-8192-6BFE5D05B4A7}"
@@ -61,7 +64,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Gemini.Sample", "sa
6164
EndProject
6265
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.AotCompatibility.Tests", "test\AutoGen.AotCompatibility.Tests\AutoGen.AotCompatibility.Tests.csproj", "{6B82F26D-5040-4453-B21B-C8D1F913CE4C}"
6366
EndProject
64-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.OpenAI.Sample", "sample\AutoGen.OpenAI.Sample\AutoGen.OpenAI.Sample.csproj", "{0E635268-351C-4A6B-A28D-593D868C2CA4}"
67+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.Sample", "sample\AutoGen.OpenAI.Sample\AutoGen.OpenAI.Sample.csproj", "{0E635268-351C-4A6B-A28D-593D868C2CA4}"
68+
EndProject
69+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.WebAPI.Sample", "sample\AutoGen.WebAPI.Sample\AutoGen.WebAPI.Sample.csproj", "{12079C18-A519-403F-BBFD-200A36A0C083}"
6570
EndProject
6671
Global
6772
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -117,6 +122,14 @@ Global
117122
{15441693-3659-4868-B6C1-B106F52FF3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
118123
{15441693-3659-4868-B6C1-B106F52FF3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
119124
{15441693-3659-4868-B6C1-B106F52FF3BA}.Release|Any CPU.Build.0 = Release|Any CPU
125+
{257FFD71-08E5-40C7-AB04-6A81A78EB410}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
126+
{257FFD71-08E5-40C7-AB04-6A81A78EB410}.Debug|Any CPU.Build.0 = Debug|Any CPU
127+
{257FFD71-08E5-40C7-AB04-6A81A78EB410}.Release|Any CPU.ActiveCfg = Release|Any CPU
128+
{257FFD71-08E5-40C7-AB04-6A81A78EB410}.Release|Any CPU.Build.0 = Release|Any CPU
129+
{E2EF5E66-683C-4DDC-8ADA-5F676502B9BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
130+
{E2EF5E66-683C-4DDC-8ADA-5F676502B9BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
131+
{E2EF5E66-683C-4DDC-8ADA-5F676502B9BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
132+
{E2EF5E66-683C-4DDC-8ADA-5F676502B9BA}.Release|Any CPU.Build.0 = Release|Any CPU
120133
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
121134
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.Build.0 = Debug|Any CPU
122135
{1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -177,6 +190,10 @@ Global
177190
{0E635268-351C-4A6B-A28D-593D868C2CA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
178191
{0E635268-351C-4A6B-A28D-593D868C2CA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
179192
{0E635268-351C-4A6B-A28D-593D868C2CA4}.Release|Any CPU.Build.0 = Release|Any CPU
193+
{12079C18-A519-403F-BBFD-200A36A0C083}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
194+
{12079C18-A519-403F-BBFD-200A36A0C083}.Debug|Any CPU.Build.0 = Debug|Any CPU
195+
{12079C18-A519-403F-BBFD-200A36A0C083}.Release|Any CPU.ActiveCfg = Release|Any CPU
196+
{12079C18-A519-403F-BBFD-200A36A0C083}.Release|Any CPU.Build.0 = Release|Any CPU
180197
EndGlobalSection
181198
GlobalSection(SolutionProperties) = preSolution
182199
HideSolutionNode = FALSE
@@ -194,6 +211,8 @@ Global
194211
{63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
195212
{6585D1A4-3D97-4D76-A688-1933B61AEB19} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
196213
{15441693-3659-4868-B6C1-B106F52FF3BA} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
214+
{257FFD71-08E5-40C7-AB04-6A81A78EB410} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
215+
{E2EF5E66-683C-4DDC-8ADA-5F676502B9BA} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
197216
{1DFABC4A-8458-4875-8DCB-59F3802DAC65} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
198217
{D36A85F9-C172-487D-8192-6BFE5D05B4A7} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
199218
{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
@@ -209,8 +228,9 @@ Global
209228
{19679B75-CE3A-4DF0-A3F0-CA369D2760A4} = {FBFEAD1F-29EB-4D99-A672-0CD8473E10B9}
210229
{6B82F26D-5040-4453-B21B-C8D1F913CE4C} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
211230
{0E635268-351C-4A6B-A28D-593D868C2CA4} = {FBFEAD1F-29EB-4D99-A672-0CD8473E10B9}
231+
{12079C18-A519-403F-BBFD-200A36A0C083} = {FBFEAD1F-29EB-4D99-A672-0CD8473E10B9}
212232
EndGlobalSection
213233
GlobalSection(ExtensibilityGlobals) = postSolution
214234
SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B}
215235
EndGlobalSection
216-
EndGlobal
236+
EndGlobal

dotnet/eng/Version.props

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<MicrosoftNETTestSdkVersion>17.7.0</MicrosoftNETTestSdkVersion>
1313
<MicrosoftDotnetInteractive>1.0.0-beta.24229.4</MicrosoftDotnetInteractive>
1414
<MicrosoftSourceLinkGitHubVersion>8.0.0</MicrosoftSourceLinkGitHubVersion>
15+
<MicrosoftASPNETCoreVersion>8.0.4</MicrosoftASPNETCoreVersion>
1516
<GoogleCloudAPIPlatformVersion>3.0.0</GoogleCloudAPIPlatformVersion>
1617
<JsonSchemaVersion>4.3.0.2</JsonSchemaVersion>
1718
</PropertyGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\..\src\AutoGen.WebAPI\AutoGen.WebAPI.csproj" />
11+
</ItemGroup>
12+
13+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Program.cs
3+
4+
using System.Runtime.CompilerServices;
5+
using AutoGen.Core;
6+
using AutoGen.Service;
7+
8+
var alice = new DummyAgent("alice");
9+
var bob = new DummyAgent("bob");
10+
11+
var builder = WebApplication.CreateBuilder(args);
12+
// Add services to the container.
13+
14+
// run endpoint at port 5000
15+
builder.WebHost.UseUrls("http://localhost:5000");
16+
var app = builder.Build();
17+
18+
app.UseAgentAsOpenAIChatCompletionEndpoint(alice);
19+
app.UseAgentAsOpenAIChatCompletionEndpoint(bob);
20+
21+
app.Run();
22+
23+
public class DummyAgent : IStreamingAgent
24+
{
25+
public DummyAgent(string name = "dummy")
26+
{
27+
Name = name;
28+
}
29+
30+
public string Name { get; }
31+
32+
public async Task<IMessage> GenerateReplyAsync(IEnumerable<IMessage> messages, GenerateReplyOptions? options = null, CancellationToken cancellationToken = default)
33+
{
34+
return new TextMessage(Role.Assistant, $"I am dummy {this.Name}", this.Name);
35+
}
36+
37+
public async IAsyncEnumerable<IMessage> GenerateStreamingReplyAsync(IEnumerable<IMessage> messages, GenerateReplyOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
38+
{
39+
var reply = $"I am dummy {this.Name}";
40+
foreach (var c in reply)
41+
{
42+
yield return new TextMessageUpdate(Role.Assistant, c.ToString(), this.Name);
43+
};
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
5+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
6+
<NoWarn>$(NoWarn);CS1591;CS1573</NoWarn>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<FrameworkReference Include="Microsoft.AspNetCore.App" Version="$(MicrosoftASPNETCoreVersion)" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\AutoGen.Core\AutoGen.Core.csproj" />
15+
</ItemGroup>
16+
</Project>
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Extension.cs
3+
4+
using AutoGen.Core;
5+
using Microsoft.AspNetCore.Builder;
6+
7+
namespace AutoGen.Service;
8+
9+
public static class Extension
10+
{
11+
/// <summary>
12+
/// Serve the agent as an OpenAI chat completion endpoint using <see cref="OpenAIChatCompletionMiddleware"/>.
13+
/// If the request path is /v1/chat/completions and model name is the same as the agent name,
14+
/// the request will be handled by the agent.
15+
/// otherwise, the request will be passed to the next middleware.
16+
/// </summary>
17+
/// <param name="app">application builder</param>
18+
/// <param name="agent"><see cref="IAgent"/></param>
19+
public static IApplicationBuilder UseAgentAsOpenAIChatCompletionEndpoint(this IApplicationBuilder app, IAgent agent)
20+
{
21+
var middleware = new OpenAIChatCompletionMiddleware(agent);
22+
return app.Use(middleware.InvokeAsync);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// OpenAIMessageConverter.cs
3+
4+
using System;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
7+
8+
namespace AutoGen.Service.OpenAI.DTO;
9+
10+
internal class OpenAIMessageConverter : JsonConverter<OpenAIMessage>
11+
{
12+
public override OpenAIMessage Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
13+
{
14+
using JsonDocument document = JsonDocument.ParseValue(ref reader);
15+
var root = document.RootElement;
16+
var role = root.GetProperty("role").GetString();
17+
var contentDocument = root.GetProperty("content");
18+
var isContentDocumentString = contentDocument.ValueKind == JsonValueKind.String;
19+
switch (role)
20+
{
21+
case "system":
22+
return JsonSerializer.Deserialize<OpenAISystemMessage>(root.GetRawText()) ?? throw new JsonException();
23+
case "user" when isContentDocumentString:
24+
return JsonSerializer.Deserialize<OpenAIUserMessage>(root.GetRawText()) ?? throw new JsonException();
25+
case "user" when !isContentDocumentString:
26+
return JsonSerializer.Deserialize<OpenAIUserMultiModalMessage>(root.GetRawText()) ?? throw new JsonException();
27+
case "assistant":
28+
return JsonSerializer.Deserialize<OpenAIAssistantMessage>(root.GetRawText()) ?? throw new JsonException();
29+
case "tool":
30+
return JsonSerializer.Deserialize<OpenAIToolMessage>(root.GetRawText()) ?? throw new JsonException();
31+
default:
32+
throw new JsonException();
33+
}
34+
}
35+
36+
public override void Write(Utf8JsonWriter writer, OpenAIMessage value, JsonSerializerOptions options)
37+
{
38+
switch (value)
39+
{
40+
case OpenAISystemMessage systemMessage:
41+
JsonSerializer.Serialize(writer, systemMessage, options);
42+
break;
43+
case OpenAIUserMessage userMessage:
44+
JsonSerializer.Serialize(writer, userMessage, options);
45+
break;
46+
case OpenAIAssistantMessage assistantMessage:
47+
JsonSerializer.Serialize(writer, assistantMessage, options);
48+
break;
49+
case OpenAIToolMessage toolMessage:
50+
JsonSerializer.Serialize(writer, toolMessage, options);
51+
break;
52+
default:
53+
throw new JsonException();
54+
}
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// OpenAIAssistantMessage.cs
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace AutoGen.Service.OpenAI.DTO;
7+
8+
internal class OpenAIAssistantMessage : OpenAIMessage
9+
{
10+
[JsonPropertyName("role")]
11+
public override string? Role { get; } = "assistant";
12+
13+
[JsonPropertyName("content")]
14+
public string? Content { get; set; }
15+
16+
[JsonPropertyName("name")]
17+
public string? Name { get; set; }
18+
19+
[JsonPropertyName("tool_calls")]
20+
public OpenAIToolCallObject[]? ToolCalls { get; set; }
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// OpenAIChatCompletion.cs
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace AutoGen.Service.OpenAI.DTO;
7+
8+
internal class OpenAIChatCompletion
9+
{
10+
[JsonPropertyName("id")]
11+
public string? ID { get; set; }
12+
13+
[JsonPropertyName("created")]
14+
public long Created { get; set; }
15+
16+
[JsonPropertyName("choices")]
17+
public OpenAIChatCompletionChoice[]? Choices { get; set; }
18+
19+
[JsonPropertyName("model")]
20+
public string? Model { get; set; }
21+
22+
[JsonPropertyName("system_fingerprint")]
23+
public string? SystemFingerprint { get; set; }
24+
25+
[JsonPropertyName("object")]
26+
public string Object { get; set; } = "chat.completion";
27+
28+
[JsonPropertyName("usage")]
29+
public OpenAIChatCompletionUsage? Usage { get; set; }
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// OpenAIChatCompletionChoice.cs
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace AutoGen.Service.OpenAI.DTO;
7+
8+
internal class OpenAIChatCompletionChoice
9+
{
10+
[JsonPropertyName("finish_reason")]
11+
public string? FinishReason { get; set; }
12+
13+
[JsonPropertyName("index")]
14+
public int Index { get; set; }
15+
16+
[JsonPropertyName("message")]
17+
public OpenAIChatCompletionMessage? Message { get; set; }
18+
19+
[JsonPropertyName("delta")]
20+
public OpenAIChatCompletionMessage? Delta { get; set; }
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// OpenAIChatCompletionMessage.cs
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace AutoGen.Service.OpenAI.DTO;
7+
8+
internal class OpenAIChatCompletionMessage
9+
{
10+
[JsonPropertyName("role")]
11+
public string Role { get; } = "assistant";
12+
13+
[JsonPropertyName("content")]
14+
public string? Content { get; set; }
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// OpenAIChatCompletionOption.cs
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace AutoGen.Service.OpenAI.DTO;
7+
8+
internal class OpenAIChatCompletionOption
9+
{
10+
[JsonPropertyName("messages")]
11+
public OpenAIMessage[]? Messages { get; set; }
12+
13+
[JsonPropertyName("model")]
14+
public string? Model { get; set; }
15+
16+
[JsonPropertyName("max_tokens")]
17+
public int? MaxTokens { get; set; }
18+
19+
[JsonPropertyName("temperature")]
20+
public float Temperature { get; set; } = 1;
21+
22+
/// <summary>
23+
/// If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a data: [DONE] message
24+
/// </summary>
25+
[JsonPropertyName("stream")]
26+
public bool? Stream { get; set; } = false;
27+
28+
[JsonPropertyName("stream_options")]
29+
public OpenAIStreamOptions? StreamOptions { get; set; }
30+
31+
[JsonPropertyName("stop")]
32+
public string[]? Stop { get; set; }
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// OpenAIChatCompletionUsage.cs
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace AutoGen.Service.OpenAI.DTO;
7+
8+
internal class OpenAIChatCompletionUsage
9+
{
10+
[JsonPropertyName("completion_tokens")]
11+
public int CompletionTokens { get; set; }
12+
13+
[JsonPropertyName("prompt_tokens")]
14+
public int PromptTokens { get; set; }
15+
16+
[JsonPropertyName("total_tokens")]
17+
public int TotalTokens { get; set; }
18+
}

0 commit comments

Comments
 (0)