Skip to content

.Net: Process: Document generation gRPC sample #11206

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

Merged
merged 31 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
06d7086
grpc demo for document generation initial
estenori Mar 25, 2025
b00e34d
updating sample to pass more than a string
estenori Mar 26, 2025
89a35ec
fixing step logic
estenori Mar 26, 2025
243aaa8
Merge branch 'main' into estenori/processes/grpcSample
estenori Mar 26, 2025
ce5f8d7
addressing formatting issues
estenori Mar 26, 2025
796d907
formatting + spelling
estenori Mar 26, 2025
abd2d27
fixing build errors/formatting
estenori Mar 26, 2025
e4dd762
missing class comment + spelling error
estenori Mar 26, 2025
53b6068
missing warnig error
estenori Mar 26, 2025
1319db3
addressing comments
esttenorio Mar 27, 2025
8303c89
readme explaining process
esttenorio Mar 27, 2025
f23a7f1
removing unnecessary package added
esttenorio Mar 27, 2025
9bccfd7
addressing comments
esttenorio Mar 28, 2025
3e1c66f
adding link to additional readme file
esttenorio Mar 28, 2025
05e80b9
Merge branch 'main' into estenori/processes/grpcSample
esttenorio Mar 28, 2025
39da360
updating solutions file
esttenorio Mar 28, 2025
2d2c12a
typo in gitignore
esttenorio Mar 28, 2025
48f2ba2
Merge branch 'main' into estenori/processes/grpcSample
esttenorio Mar 28, 2025
a18f5e6
Merge branch 'main' into estenori/processes/grpcSample
esttenorio Mar 31, 2025
cb9969f
Apply learning website references
esttenorio Mar 31, 2025
bb07422
build errors fix
esttenorio Mar 31, 2025
6096bde
Merge branch 'main' into estenori/processes/grpcSample
esttenorio Mar 31, 2025
dc6d74f
serializing objects internally - dapr working
esttenorio Apr 1, 2025
e9ab500
working with localruntime
esttenorio Apr 2, 2025
43154d7
dapr integration tests working
esttenorio Apr 2, 2025
f072057
Merge pull request #1 from esttenorio/estenori/processes/expSerializa…
esttenorio Apr 2, 2025
60352d3
Merge branch 'main' into estenori/processes/grpcSample
esttenorio Apr 2, 2025
df74ef2
removing unnecesary import
esttenorio Apr 2, 2025
540b5dc
removing commented code
esttenorio Apr 2, 2025
ea36ff1
fixing map step in local and dapr runtime after serialization changes
esttenorio Apr 7, 2025
233408b
Merge branch 'main' into estenori/processes/grpcSample
esttenorio Apr 7, 2025
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
6 changes: 6 additions & 0 deletions dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
<PackageVersion Include="Dapr.AspNetCore" Version="1.14.0" />
<PackageVersion Include="FastBertTokenizer" Version="1.0.28" />
<PackageVersion Include="Google.Apis.Auth" Version="1.69.0" />
<PackageVersion Include="Google.Protobuf" Version="3.27.0" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.70.0" />
<PackageVersion Include="Grpc.AspNetCore.Server" Version="2.70.0" />
<PackageVersion Include="Grpc.AspNetCore.Server.Reflection" Version="2.70.0" />
<PackageVersion Include="Grpc.Net.Client" Version="2.70.0" />
<PackageVersion Include="Grpc.Tools" Version="2.70.0" />
<PackageVersion Include="mcpdotnet" Version="1.0.1.3" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.13" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.13" />
Expand Down
27 changes: 27 additions & 0 deletions dotnet/SK-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlServerIntegrationTests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PineconeIntegrationTests", "src\VectorDataIntegrationTests\PineconeIntegrationTests\PineconeIntegrationTests.csproj", "{E9A74E0C-BC02-4DDD-A487-89847EDF8026}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProcessWithCloudEvents", "ProcessWithCloudEvents", "{8DAEAD5B-5B0C-45D2-A6DD-668E84B5FB27}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessWithCloudEvents.Processes", "samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Processes\ProcessWithCloudEvents.Processes.csproj", "{31F6608A-FD36-F529-A5FC-C954A0B5E29E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessWithCloudEvents.Grpc", "samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Grpc\ProcessWithCloudEvents.Grpc.csproj", "{08D84994-794A-760F-95FD-4EFA8998A16D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1342,6 +1348,24 @@ Global
{E9A74E0C-BC02-4DDD-A487-89847EDF8026}.Publish|Any CPU.Build.0 = Release|Any CPU
{E9A74E0C-BC02-4DDD-A487-89847EDF8026}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9A74E0C-BC02-4DDD-A487-89847EDF8026}.Release|Any CPU.Build.0 = Release|Any CPU
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Publish|Any CPU.Build.0 = Release|Any CPU
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31F6608A-FD36-F529-A5FC-C954A0B5E29E}.Release|Any CPU.Build.0 = Release|Any CPU
{08D84994-794A-760F-95FD-4EFA8998A16D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08D84994-794A-760F-95FD-4EFA8998A16D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08D84994-794A-760F-95FD-4EFA8998A16D}.Publish|Any CPU.ActiveCfg = Release|Any CPU
{08D84994-794A-760F-95FD-4EFA8998A16D}.Publish|Any CPU.Build.0 = Release|Any CPU
{08D84994-794A-760F-95FD-4EFA8998A16D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08D84994-794A-760F-95FD-4EFA8998A16D}.Release|Any CPU.Build.0 = Release|Any CPU
{D1786E2B-CAA0-4B2D-A974-9845EB9E420F}.Debug|Any CPU.ActiveCfg = Debug
{D1786E2B-CAA0-4B2D-A974-9845EB9E420F}.Debug|Any CPU.Build.0 = Debug
{D1786E2B-CAA0-4B2D-A974-9845EB9E420F}.Publish|Any CPU.ActiveCfg = Release
{D1786E2B-CAA0-4B2D-A974-9845EB9E420F}.Publish|Any CPU.Build.0 = Release
{D1786E2B-CAA0-4B2D-A974-9845EB9E420F}.Release|Any CPU.ActiveCfg = Release
{D1786E2B-CAA0-4B2D-A974-9845EB9E420F}.Release|Any CPU.Build.0 = Release
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1525,6 +1549,9 @@ Global
{B16AC373-3DA8-4505-9510-110347CD635D} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
{A5E6193C-8431-4C6E-B674-682CB41EAA0C} = {4F381919-F1BE-47D8-8558-3187ED04A84F}
{E9A74E0C-BC02-4DDD-A487-89847EDF8026} = {4F381919-F1BE-47D8-8558-3187ED04A84F}
{8DAEAD5B-5B0C-45D2-A6DD-668E84B5FB27} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
{31F6608A-FD36-F529-A5FC-C954A0B5E29E} = {8DAEAD5B-5B0C-45D2-A6DD-668E84B5FB27}
{08D84994-794A-760F-95FD-4EFA8998A16D} = {8DAEAD5B-5B0C-45D2-A6DD-668E84B5FB27}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
Expand Down
9 changes: 9 additions & 0 deletions dotnet/dapr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apps:
- appDirPath: samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Grpc
appID: processwithcloudevents-grpc
appPort: 58641
appProtocol: h2c
command:
- dotnet
- run
version: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) Microsoft. All rights reserved.
using Grpc.Net.Client;
using Microsoft.SemanticKernel;
using ProcessWithCloudEvents.Grpc.DocumentationGenerator;
using ProcessWithCloudEvents.Processes;

namespace ProcessWithCloudEvents.Grpc.Clients;

public class DocumentGenerationGrpcClient : IExternalKernelProcessMessageChannel
{
private GrpcChannel? _grpcChannel;
private GrpcDocumentationGeneration.GrpcDocumentationGenerationClient? _grpcClient;

/// <inheritdoc/>
public async ValueTask Initialize()
{
this._grpcChannel = GrpcChannel.ForAddress("http://localhost:58641");
this._grpcClient = new GrpcDocumentationGeneration.GrpcDocumentationGenerationClient(this._grpcChannel);
}

/// <inheritdoc/>
public async ValueTask Uninitialize()
{
if (this._grpcChannel != null)
{
await this._grpcChannel.ShutdownAsync();
}
}

/// <inheritdoc/>
public async Task EmitExternalEventAsync(string externalTopicEvent, KernelProcessProxyMessage eventData)
{
if (this._grpcClient != null)
{
switch (externalTopicEvent)
{
case DocumentGenerationProcess.DocGenerationTopics.RequestUserReview:
await this._grpcClient.RequestUserReviewDocumentationFromProcessAsync(new()
{
Title = "Document for user review",
AssistantMessage = "",
Content = eventData.EventData?.ToString(),
ProcessData = new() { ProcessId = eventData.ProcessId }
});

return;

case DocumentGenerationProcess.DocGenerationTopics.PublishDocumentation:
await this._grpcClient.PublishDocumentationAsync(new()
{
ProcessData = new() { ProcessId = eventData.ProcessId }
});
return;

default:
break;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>
$(NoWarn);CA2007,CA1861,VSTHRD111,SKEXP0001,SKEXP0010,SKEXP0020,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0080,SKEXP0110
</NoWarn>
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<Protobuf Include="Protos\documentationGenerator.proto" GrpcServices="Both" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Connectors\Connectors.OpenAI\Connectors.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Abstractions\Process.Abstractions.csproj" />
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Core\Process.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Runtime.Dapr\Process.Runtime.Dapr.csproj" />
<ProjectReference Include="..\ProcessWithCloudEvents.Processes\ProcessWithCloudEvents.Processes.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Dapr.Actors" />
<PackageReference Include="Dapr.Actors.AspNetCore" />
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.AspNetCore" />
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" />
<PackageReference Include="Grpc.Net.Client" />
<PackageReference Include="Grpc.Tools">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;
using ProcessWithCloudEvents.Grpc.Services;
using ProcessWithCloudEvents.Grpc.Clients;

var builder = WebApplication.CreateBuilder(args);

var config = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.AddEnvironmentVariables()
.Build();

// Configure logging
builder.Services.AddLogging((logging) =>
{
logging.AddConsole();
logging.AddDebug();
});

// Configure the Kernel with DI. This is required for dependency injection to work with processes.
builder.Services.AddKernel();

//var openAIOptions = config.GetValid<OpenAIOptions>(OpenAIOptions.SectionName);
//builder.Services.AddOpenAIChatCompletion(openAIOptions.ChatModelId, openAIOptions.ApiKey);
builder.Services.AddSingleton<DocumentGenerationService>();
// Injecting SK Process custom grpc client IExternalKernelProcessMessageChannel implementation
builder.Services.AddSingleton<IExternalKernelProcessMessageChannel, DocumentGenerationGrpcClient>();

// Configure Dapr
builder.Services.AddActors(static options =>
{
// Register the actors required to run Processes
options.AddProcessActors();
});

// Add grpc related services.
builder.Services.AddGrpc();
builder.Services.AddGrpcReflection();

var app = builder.Build();

// Grpc services mapping
app.MapGrpcReflectionService();
app.MapGrpcService<DocumentGenerationService>();

// Dapr actors related mapping
app.MapActorsHandlers();
app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
syntax = "proto3";

option csharp_namespace = "ProcessWithCloudEvents.Grpc.DocumentationGenerator";

service GrpcDocumentationGeneration {
rpc UserRequestFeatureDocumentation (FeatureDocumentationRequest) returns (ProcessData);
rpc RequestUserReviewDocumentationFromProcess (DocumentationContentRequest) returns (Empty);
rpc RequestUserReviewDocumentation (ProcessData) returns (stream DocumentationContentRequest);
rpc UserReviewedDocumentation (DocumentationApprovalRequest) returns (Empty);
rpc PublishDocumentation (DocumentationContentRequest) returns (Empty);
rpc ReceivePublishedDocumentation (ProcessData) returns (stream DocumentationContentRequest);
}

message FeatureDocumentationRequest {
string title = 1;
string userDescription = 2;
string content = 3;
string processId = 10;
}

message DocumentationContentRequest {
string title = 1;
string content = 2;
string assistantMessage = 3;
ProcessData processData = 10;
}

message DocumentationApprovalRequest {
bool documentationApproved = 1;
string reason = 2;
ProcessData processData = 10;
}

message ProcessData {
string processId = 1;
}

message Empty {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Process With Cloud Events - using gRPC

For using gRPC, this demo follows the guidelines suggested for any [gRPC ASP.NET Core App](https://learn.microsoft.com/en-us/aspnet/core/grpc/test-tools?view=aspnetcore-9.0).

Which for this demo means:

- Making use of `builder.Services.AddGrpcReflection()` and `app.MapGrpcReflectionService()`
- Making use of [`gRPCui`](https://github.com/fullstorydev/grpcui) for testing

## Explanation

This demo showcases how SK Process Framework could interact with a gRPC Server and clients.

The main difference of this demo is the custom implementation of the gRPC Server and client used internally by the SK Process in the SK Proxy Step.

Main gRPC components:

- `documentationGenerator.proto`: `<root>\dotnet\samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Grpc\Protos\documentationGenerator.proto`
- gRPC Server: `<root>\dotnet\samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Grpc\Services\DocumentGenerationService.cs`
- gRPC Client/IExternalKernelProcessMessageChannel implementation: `<root>\dotnet\samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.Grpc\Clients\DocumentGenerationGrpcClient.cs`

### SK Process and gRPC Events

``` mermaid

sequenceDiagram
participant grpcClient as gRPC Client
box Server
participant grpcServer as gRPC Server
participant SKP as SK Process
end

grpcClient->>grpcServer: UserRequestFeatureDocumentation <br/>gRPC
grpcServer->>SKP: StartDocumentGeneration <br/>SK event
SKP->>grpcServer: RequestUserReview (SK Topic)/<br/>RequestUserReviewDocumentationFromProcess (gRPC)
grpcServer->>grpcClient: RequestUserReviewDocumentation <br/>gRPC
grpcClient->>grpcServer: UserReviewedDocumentation <br/>gRPC
grpcServer->>SKP: UserApprovedDocument/UserRejectedDocument <br/>SK event
SKP->>grpcServer: PublishDocumentation (SK Topic)/<br/>PublishDocumentation (gRPC)
grpcServer->>grpcClient: ReceivePublishedDocumentation <br/>gRPC
```
1. When the `UserRequestFeatureDocumentation` gRPC request is received from the gRPC client, the server initiates an SK Process and emits the `StartDocumentGeneration` SK event.
2. The `RequestUserReview` topic is emitted when the `DocumentationApproved` event is triggered during the `ProofReadDocumentationStep`. This event invokes the `RequestUserReviewDocumentationFromProcess` gRPC method to communicate with the server.
3. The `RequestUserReviewDocumentationFromProcess` method updates the shared stream, which is used to communicate with the subscribers of `RequestUserReviewDocumentation`. The gRPC client then receives the document for review and approval.
4. The gRPC client can approve or reject the document using the `UserReviewedDocumentation` method to communicate with the server. The server then sends the `UserApprovedDocument` or `UserRejectedDocument` SK event to the SK Process.
5. The SK Process resumes, and the `PublishDocumentationStep` now has all the necessary parameters to execute. Upon execution, the `PublishDocumentation` topic is triggered, invoking the `PublishDocumentation` method on the gRPC server.
6. The PublishDocumentation method updates the shared stream used by `ReceivePublishedDocumentation`, ensuring that all subscribers receive the update of the latest published document
## Demo
### Requirements

- Have Dapr setup ready
- Build and Run the app
- Install and run `gRPCui` listening to the address `localhost:58640`:
```
./grpcui.exe -plaintext localhost:58641
```

### Usage

1. Build and run the app
2. Open 2 windows of `gRPCui` with the following methods:
- Window 1:
- Method name: `UserRequestFeatureDocumentation` and `UserReviewedDocumentation`
- Window 2:
- Method name: `RequestUserReviewDocumentation`
- Window 3:
- Method name: `ReceivePublishedDocumentation`

3. Select a process id to be used with all methods. Example: processId = "100"
4. Execute different methods in the following order:
1. `RequestUserReviewDocumentation` with Request Data:
```json
{
"processId": "100"
}
```
This will subscribe to any request for review done for the specific process id and a response will be received when the process emits a notification.
Set timeout to 30 seconds.

2. `UserRequestFeatureDocumentation` with Request Data:
```json
{
"title": "100",
"userDescription": "100",
"content": "100",
"processId": "100",
}
```
This request will kickstart the creation of a new process with the specific processId passing an initial event to the SK process.

5. Once the `RequestUserReviewDocumentation` is received, execute the following methods:
1. `ReceivePublishedDocumentation` with Request Data:
```json
{
"processId": "100"
}
```
This will subscribe to any request for review done for the specific process id and a response will be received when the process emits a notification.
Set timeout to 30 seconds.

2. `UserReviewedDocumentation` with Request Data:
```json
{
"documentationApproved": true,
"reason": "",
"processData":
{
"processId": "100"
}
}
```




### Debugging

For debugging and be able to set breakpoints in different stages of the app, you can:

- Install the [Visual Studio Dapr Extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vs-dapr) and make use of it by making use of the `<root>\dotnet\dapr.yaml` file already in the repository.

or

- Set the `ProcessWithCloudEvents.Grpc` as startup app, run and attach the Visual Studio debugger:
```
dapr run --app-id processwithcloudevents-grpc --app-port 58641 --app-protocol h2c -- dotnet run --no-build
```
Loading