Skip to content

Commit 7b3c378

Browse files
esttenorioestenori
andauthored
.Net: Process: Document generation gRPC sample (#11206)
### Motivation and Context Demo showcasing how to emit/receive cloud events. Demo showcases a simple gRPC scenario. Adding readme with details of gRPC / SK Process interactions Fixes #11223 ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄 --------- Co-authored-by: Estefania Tenorio <[email protected]>
1 parent 0ff97c7 commit 7b3c378

37 files changed

+1432
-97
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,7 @@ swa-cli.config.json
496496
/python/.devcontainer/*
497497

498498
# kiota workspace files
499-
**/.kiota
499+
**/.kiota
500+
501+
# dapr extension files
502+
**/dapr.yaml

dotnet/Directory.Packages.props

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
<PackageVersion Include="EntityFramework" Version="6.5.1" />
3030
<PackageVersion Include="FastBertTokenizer" Version="1.0.28" />
3131
<PackageVersion Include="Google.Apis.Auth" Version="1.69.0" />
32+
<PackageVersion Include="Google.Protobuf" Version="3.27.0" />
33+
<PackageVersion Include="Grpc.AspNetCore" Version="2.70.0" />
34+
<PackageVersion Include="Grpc.AspNetCore.Server" Version="2.70.0" />
35+
<PackageVersion Include="Grpc.AspNetCore.Server.Reflection" Version="2.70.0" />
36+
<PackageVersion Include="Grpc.Tools" Version="2.70.0" />
3237
<PackageVersion Include="ModelContextProtocol" Version="0.1.0-preview.4" />
3338
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.13" />
3439
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.14" />
@@ -135,7 +140,7 @@
135140
<PackageVersion Include="Microsoft.OpenApi.ApiManifest" Version="0.5.6-preview" />
136141
<PackageVersion Include="Microsoft.Plugins.Manifest" Version="1.0.0-rc3" />
137142
<PackageVersion Include="Google.Apis.CustomSearchAPI.v1" Version="1.68.0.3520" />
138-
<PackageVersion Include="Grpc.Net.Client" Version="2.66.0" />
143+
<PackageVersion Include="Grpc.Net.Client" Version="2.70.0" />
139144
<PackageVersion Include="protobuf-net" Version="3.2.45" />
140145
<PackageVersion Include="protobuf-net.Reflection" Version="3.2.12" />
141146
<PackageVersion Include="YamlDotNet" Version="15.3.0" />

dotnet/SK-dotnet.sln

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCPClient", "samples\Demos\
520520
{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981} = {12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981}
521521
EndProjectSection
522522
EndProject
523+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProcessWithCloudEvents", "ProcessWithCloudEvents", "{7C092DD9-9985-4D18-A817-15317D984149}"
524+
ProjectSection(SolutionItems) = preProject
525+
samples\Demos\ProcessWithCloudEvents\README.md = samples\Demos\ProcessWithCloudEvents\README.md
526+
EndProjectSection
527+
EndProject
528+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessWithCloudEvents.Processes", "samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Processes\ProcessWithCloudEvents.Processes.csproj", "{31F6608A-FD36-F529-A5FC-C954A0B5E29E}"
529+
EndProject
530+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessWithCloudEvents.Grpc", "samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Grpc\ProcessWithCloudEvents.Grpc.csproj", "{08D84994-794A-760F-95FD-4EFA8998A16D}"
531+
EndProject
523532
Global
524533
GlobalSection(SolutionConfigurationPlatforms) = preSolution
525534
Debug|Any CPU = Debug|Any CPU
@@ -1427,6 +1436,18 @@ Global
14271436
{B06770D5-2F3E-4271-9F6B-3AA9E716176F}.Publish|Any CPU.Build.0 = Release|Any CPU
14281437
{B06770D5-2F3E-4271-9F6B-3AA9E716176F}.Release|Any CPU.ActiveCfg = Release|Any CPU
14291438
{B06770D5-2F3E-4271-9F6B-3AA9E716176F}.Release|Any CPU.Build.0 = Release|Any CPU
1439+
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1440+
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Debug|Any CPU.Build.0 = Debug|Any CPU
1441+
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Publish|Any CPU.ActiveCfg = Release|Any CPU
1442+
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Publish|Any CPU.Build.0 = Release|Any CPU
1443+
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Release|Any CPU.ActiveCfg = Release|Any CPU
1444+
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Release|Any CPU.Build.0 = Release|Any CPU
1445+
{08D84994-794A-760F-95FD-4EFA8998A16D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1446+
{08D84994-794A-760F-95FD-4EFA8998A16D}.Debug|Any CPU.Build.0 = Debug|Any CPU
1447+
{08D84994-794A-760F-95FD-4EFA8998A16D}.Publish|Any CPU.ActiveCfg = Release|Any CPU
1448+
{08D84994-794A-760F-95FD-4EFA8998A16D}.Publish|Any CPU.Build.0 = Release|Any CPU
1449+
{08D84994-794A-760F-95FD-4EFA8998A16D}.Release|Any CPU.ActiveCfg = Release|Any CPU
1450+
{08D84994-794A-760F-95FD-4EFA8998A16D}.Release|Any CPU.Build.0 = Release|Any CPU
14301451
EndGlobalSection
14311452
GlobalSection(SolutionProperties) = preSolution
14321453
HideSolutionNode = FALSE
@@ -1621,6 +1642,9 @@ Global
16211642
{879545ED-D429-49B1-96F1-2EC55FFED31D} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
16221643
{12C7E0C7-A7DF-3BC3-0D4B-1A706BCE6981} = {879545ED-D429-49B1-96F1-2EC55FFED31D}
16231644
{B06770D5-2F3E-4271-9F6B-3AA9E716176F} = {879545ED-D429-49B1-96F1-2EC55FFED31D}
1645+
{7C092DD9-9985-4D18-A817-15317D984149} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
1646+
{31F6608A-FD36-F529-A5FC-C954A0B5E29E} = {7C092DD9-9985-4D18-A817-15317D984149}
1647+
{08D84994-794A-760F-95FD-4EFA8998A16D} = {7C092DD9-9985-4D18-A817-15317D984149}
16241648
EndGlobalSection
16251649
GlobalSection(ExtensibilityGlobals) = postSolution
16261650
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
using Grpc.Net.Client;
3+
using Microsoft.SemanticKernel;
4+
using ProcessWithCloudEvents.Grpc.DocumentationGenerator;
5+
using ProcessWithCloudEvents.Processes;
6+
using ProcessWithCloudEvents.Processes.Models;
7+
8+
namespace ProcessWithCloudEvents.Grpc.Clients;
9+
10+
/// <summary>
11+
/// Client that implements the <see cref="IExternalKernelProcessMessageChannel"/> interface used internally by the SK process
12+
/// to emit events to external systems.<br/>
13+
/// This implementation is an example of a gRPC client that emits events to a gRPC server
14+
/// </summary>
15+
public class DocumentGenerationGrpcClient : IExternalKernelProcessMessageChannel
16+
{
17+
private GrpcChannel? _grpcChannel;
18+
private GrpcDocumentationGeneration.GrpcDocumentationGenerationClient? _grpcClient;
19+
20+
/// <inheritdoc/>
21+
public async ValueTask Initialize()
22+
{
23+
this._grpcChannel = GrpcChannel.ForAddress("http://localhost:58641");
24+
this._grpcClient = new GrpcDocumentationGeneration.GrpcDocumentationGenerationClient(this._grpcChannel);
25+
}
26+
27+
/// <inheritdoc/>
28+
public async ValueTask Uninitialize()
29+
{
30+
if (this._grpcChannel != null)
31+
{
32+
await this._grpcChannel.ShutdownAsync();
33+
}
34+
}
35+
36+
/// <inheritdoc/>
37+
public async Task EmitExternalEventAsync(string externalTopicEvent, KernelProcessProxyMessage message)
38+
{
39+
if (this._grpcClient != null && message.EventData != null)
40+
{
41+
switch (externalTopicEvent)
42+
{
43+
case DocumentGenerationProcess.DocGenerationTopics.RequestUserReview:
44+
var requestDocument = message.EventData.ToObject() as DocumentInfo;
45+
if (requestDocument != null)
46+
{
47+
await this._grpcClient.RequestUserReviewDocumentationFromProcessAsync(new()
48+
{
49+
Title = requestDocument.Title,
50+
AssistantMessage = "Document ready for user revision. Approve or reject document",
51+
Content = requestDocument.Content,
52+
ProcessData = new() { ProcessId = message.ProcessId }
53+
});
54+
}
55+
return;
56+
57+
case DocumentGenerationProcess.DocGenerationTopics.PublishDocumentation:
58+
var publishedDocument = message.EventData.ToObject() as DocumentInfo;
59+
if (publishedDocument != null)
60+
{
61+
await this._grpcClient.PublishDocumentationAsync(new()
62+
{
63+
Title = publishedDocument.Title,
64+
AssistantMessage = "Published Document Ready",
65+
Content = publishedDocument.Content,
66+
ProcessData = new() { ProcessId = message.ProcessId }
67+
});
68+
}
69+
return;
70+
}
71+
}
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.ComponentModel.DataAnnotations;
4+
5+
namespace ProcessWithCloudEvents.Grpc.Extensions;
6+
7+
/// <summary>
8+
/// Class with extension methods for app configuration.
9+
/// </summary>
10+
public static class ConfigurationExtensions
11+
{
12+
/// <summary>
13+
/// Returns <typeparamref name="TOptions"/> if it's valid or throws <see cref="ValidationException"/>.
14+
/// </summary>
15+
public static TOptions GetValid<TOptions>(this IConfigurationRoot configurationRoot, string sectionName)
16+
{
17+
var options = configurationRoot.GetSection(sectionName).Get<TOptions>()!;
18+
19+
Validator.ValidateObject(options, new(options));
20+
21+
return options;
22+
}
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.ComponentModel.DataAnnotations;
4+
5+
namespace ProcessWithCloudEvents.Grpc.Options;
6+
7+
/// <summary>
8+
/// Configuration for OpenAI chat completion service.
9+
/// </summary>
10+
public class OpenAIOptions
11+
{
12+
public const string SectionName = "OpenAI";
13+
14+
[Required]
15+
public string ChatModelId { get; set; } = string.Empty;
16+
17+
[Required]
18+
public string ApiKey { get; set; } = string.Empty;
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<NoWarn>
8+
$(NoWarn);CA2007,CS1591,CA1861,VSTHRD111,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0080,SKEXP0110
9+
</NoWarn>
10+
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<Protobuf Include="Protos\documentationGenerator.proto" GrpcServices="Both" />
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<ProjectReference Include="..\..\..\..\src\Connectors\Connectors.OpenAI\Connectors.OpenAI.csproj" />
19+
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Abstractions\Process.Abstractions.csproj" />
20+
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Core\Process.Core.csproj" />
21+
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Runtime.Dapr\Process.Runtime.Dapr.csproj" />
22+
<ProjectReference Include="..\ProcessWithCloudEvents.Processes\ProcessWithCloudEvents.Processes.csproj" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<PackageReference Include="Dapr.Actors" />
27+
<PackageReference Include="Dapr.Actors.AspNetCore" />
28+
<PackageReference Include="Google.Protobuf" />
29+
<PackageReference Include="Grpc.AspNetCore" />
30+
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" />
31+
<PackageReference Include="Grpc.Net.Client" />
32+
<PackageReference Include="Grpc.Tools">
33+
<PrivateAssets>all</PrivateAssets>
34+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
35+
</PackageReference>
36+
</ItemGroup>
37+
38+
<ItemGroup>
39+
<Folder Include="Media\" />
40+
</ItemGroup>
41+
42+
</Project>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel;
4+
using ProcessWithCloudEvents.Grpc.Clients;
5+
using ProcessWithCloudEvents.Grpc.Extensions;
6+
using ProcessWithCloudEvents.Grpc.Options;
7+
using ProcessWithCloudEvents.Grpc.Services;
8+
9+
var builder = WebApplication.CreateBuilder(args);
10+
11+
var config = new ConfigurationBuilder()
12+
.AddUserSecrets<Program>()
13+
.AddEnvironmentVariables()
14+
.Build();
15+
16+
// Configure logging
17+
builder.Services.AddLogging((logging) =>
18+
{
19+
logging.AddConsole();
20+
logging.AddDebug();
21+
});
22+
23+
var openAIOptions = config.GetValid<OpenAIOptions>(OpenAIOptions.SectionName);
24+
25+
// Configure the Kernel with DI. This is required for dependency injection to work with processes.
26+
builder.Services.AddKernel();
27+
builder.Services.AddOpenAIChatCompletion(openAIOptions.ChatModelId, openAIOptions.ApiKey);
28+
29+
builder.Services.AddSingleton<DocumentGenerationService>();
30+
// Injecting SK Process custom grpc client IExternalKernelProcessMessageChannel implementation
31+
builder.Services.AddSingleton<IExternalKernelProcessMessageChannel, DocumentGenerationGrpcClient>();
32+
33+
// Configure Dapr
34+
builder.Services.AddActors(static options =>
35+
{
36+
// Register the actors required to run Processes
37+
options.AddProcessActors();
38+
});
39+
40+
// Add grpc related services.
41+
builder.Services.AddGrpc();
42+
builder.Services.AddGrpcReflection();
43+
44+
var app = builder.Build();
45+
46+
// Grpc services mapping
47+
app.MapGrpcReflectionService();
48+
app.MapGrpcService<DocumentGenerationService>();
49+
50+
// Dapr actors related mapping
51+
app.MapActorsHandlers();
52+
app.Run();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
syntax = "proto3";
2+
3+
option csharp_namespace = "ProcessWithCloudEvents.Grpc.DocumentationGenerator";
4+
5+
service GrpcDocumentationGeneration {
6+
rpc UserRequestFeatureDocumentation (FeatureDocumentationRequest) returns (ProcessData);
7+
rpc RequestUserReviewDocumentationFromProcess (DocumentationContentRequest) returns (Empty);
8+
rpc RequestUserReviewDocumentation (ProcessData) returns (stream DocumentationContentRequest);
9+
rpc UserReviewedDocumentation (DocumentationApprovalRequest) returns (Empty);
10+
rpc PublishDocumentation (DocumentationContentRequest) returns (Empty);
11+
rpc ReceivePublishedDocumentation (ProcessData) returns (stream DocumentationContentRequest);
12+
}
13+
14+
message FeatureDocumentationRequest {
15+
string title = 1;
16+
string userDescription = 2;
17+
string content = 3;
18+
string processId = 10;
19+
}
20+
21+
message DocumentationContentRequest {
22+
string title = 1;
23+
string content = 2;
24+
string assistantMessage = 3;
25+
ProcessData processData = 10;
26+
}
27+
28+
message DocumentationApprovalRequest {
29+
bool documentationApproved = 1;
30+
string reason = 2;
31+
ProcessData processData = 10;
32+
}
33+
34+
message ProcessData {
35+
string processId = 1;
36+
}
37+
38+
message Empty {}

0 commit comments

Comments
 (0)