Skip to content

Commit 664d10f

Browse files
committed
updating existing demo for dapr validation
1 parent 240f1b5 commit 664d10f

10 files changed

+308
-4
lines changed

dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Grpc/Clients/DocumentGenerationGrpcClient.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
using Grpc.Net.Client;
33
using Microsoft.SemanticKernel;
4-
using ProcessWithCloudEvents.Grpc.DocumentationGenerator;
4+
using ProcessWithCloudEvents.Grpc.Contract;
55
using ProcessWithCloudEvents.Processes;
66
using ProcessWithCloudEvents.Processes.Models;
77

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
using Grpc.Net.Client;
3+
using Microsoft.SemanticKernel;
4+
using ProcessWithCloudEvents.Grpc.Contract;
5+
using ProcessWithCloudEvents.Processes;
6+
7+
namespace ProcessWithCloudEvents.Grpc.Clients;
8+
9+
/// <summary>
10+
/// Client that implements the <see cref="IExternalKernelProcessMessageChannel"/> interface used internally by the SK process
11+
/// to emit events to external systems.<br/>
12+
/// This implementation is an example of a gRPC client that emits events to a gRPC server
13+
/// </summary>
14+
public class TeacherStudentInteractionGrpcClient : IExternalKernelProcessMessageChannel
15+
{
16+
private GrpcChannel? _grpcChannel;
17+
private GrpcTeacherStudentInteraction.GrpcTeacherStudentInteractionClient? _grpcClient;
18+
19+
/// <inheritdoc/>
20+
public async ValueTask Initialize()
21+
{
22+
this._grpcChannel = GrpcChannel.ForAddress("http://localhost:58641");
23+
this._grpcClient = new GrpcTeacherStudentInteraction.GrpcTeacherStudentInteractionClient(this._grpcChannel);
24+
}
25+
26+
/// <inheritdoc/>
27+
public async ValueTask Uninitialize()
28+
{
29+
if (this._grpcChannel != null)
30+
{
31+
await this._grpcChannel.ShutdownAsync();
32+
}
33+
}
34+
35+
/// <inheritdoc/>
36+
public async Task EmitExternalEventAsync(string externalTopicEvent, KernelProcessProxyMessage message)
37+
{
38+
if (this._grpcClient != null && message.EventData != null)
39+
{
40+
switch (externalTopicEvent)
41+
{
42+
case TeacherStudentProcess.InteractionTopics.AgentResponseMessage:
43+
var agentResponse = message.EventData.ToObject() as ChatMessageContent;
44+
if (agentResponse != null)
45+
{
46+
await this._grpcClient.PublishStudentAgentResponseFromProcessAsync(new()
47+
{
48+
User = User.Student,
49+
Content = agentResponse.Content,
50+
});
51+
}
52+
return;
53+
54+
case TeacherStudentProcess.InteractionTopics.AgentErrorMessage:
55+
var agentErrorResponse = message.EventData.ToObject() as string;
56+
if (agentErrorResponse != null)
57+
{
58+
await this._grpcClient.PublishStudentAgentResponseFromProcessAsync(new()
59+
{
60+
User = User.Student,
61+
Content = $"ERROR: {agentErrorResponse}",
62+
});
63+
}
64+
return;
65+
}
66+
}
67+
}
68+
}

dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Grpc/ProcessWithCloudEvents.Grpc.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
<ItemGroup>
1414
<Protobuf Include="Protos\documentationGenerator.proto" GrpcServices="Both" />
15+
<Protobuf Include="Protos\teacherStudentInteraction.proto" GrpcServices="Both" />
1516
</ItemGroup>
1617

1718
<ItemGroup>

dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Grpc/Program.cs

+18-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
using ProcessWithCloudEvents.Grpc.Options;
77
using ProcessWithCloudEvents.Grpc.Services;
88
using ProcessWithCloudEvents.Processes;
9+
using Microsoft.SemanticKernel.Agents.OpenAI;
10+
using OpenAI;
11+
using System.ClientModel;
12+
using Microsoft.SemanticKernel.Agents;
913

1014
var builder = WebApplication.CreateBuilder(args);
1115

@@ -27,9 +31,17 @@
2731
builder.Services.AddKernel();
2832
builder.Services.AddOpenAIChatCompletion(openAIOptions.ChatModelId, openAIOptions.ApiKey);
2933

34+
// Setup for using Agent Steps in SK Process
35+
var openAIClient = OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential(openAIOptions.ApiKey));
36+
builder.Services.AddSingleton<OpenAIClient>(openAIClient);
37+
builder.Services.AddTransient<AgentFactory, OpenAIAssistantAgentFactory>();
38+
39+
// Grpc setup
3040
builder.Services.AddSingleton<DocumentGenerationService>();
41+
builder.Services.AddSingleton<TeacherStudentInteractionService>();
3142
// Injecting SK Process custom grpc client IExternalKernelProcessMessageChannel implementation
32-
builder.Services.AddSingleton<IExternalKernelProcessMessageChannel, DocumentGenerationGrpcClient>();
43+
//builder.Services.AddSingleton<IExternalKernelProcessMessageChannel, DocumentGenerationGrpcClient>();
44+
builder.Services.AddSingleton<IExternalKernelProcessMessageChannel, TeacherStudentInteractionGrpcClient>();
3345

3446
// Configure Dapr
3547
builder.Services.AddDaprKernelProcesses();
@@ -44,6 +56,10 @@
4456
{
4557
return DocumentGenerationProcess.CreateProcessBuilder().Build();
4658
});
59+
builder.Services.AddKeyedSingleton<KernelProcess>(TeacherStudentProcess.Key, (sp, key) =>
60+
{
61+
return TeacherStudentProcess.CreateProcessBuilder().Build();
62+
});
4763

4864
// Enabling CORS for grpc-web when using webApp as client, remove if not needed
4965
builder.Services.AddCors(o => o.AddPolicy("AllowAll", builder =>
@@ -67,6 +83,7 @@
6783
// Enabling CORS for grpc-web, remove if not needed
6884
app.MapGrpcReflectionService().RequireCors("AllowAll");
6985
app.MapGrpcService<DocumentGenerationService>().EnableGrpcWeb().RequireCors("AllowAll");
86+
app.MapGrpcService<TeacherStudentInteractionService>().EnableGrpcWeb().RequireCors("AllowAll");
7087

7188
// Dapr actors related mapping
7289
app.MapActorsHandlers();

dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Grpc/Protos/documentationGenerator.proto

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
syntax = "proto3";
22

3-
option csharp_namespace = "ProcessWithCloudEvents.Grpc.DocumentationGenerator";
3+
package ProcessWithCloudEvents.Grpc.Contract;
4+
option csharp_namespace = "ProcessWithCloudEvents.Grpc.Contract";
45

56
service GrpcDocumentationGeneration {
67
rpc UserRequestFeatureDocumentation (FeatureDocumentationRequest) returns (ProcessData);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
syntax = "proto3";
2+
3+
package ProcessWithCloudEvents.Grpc.Contract;
4+
option csharp_namespace = "ProcessWithCloudEvents.Grpc.Contract";
5+
6+
service GrpcTeacherStudentInteraction {
7+
rpc StartProcess (ProcessDetails) returns (ProcessDetails);
8+
rpc RequestStudentAgentResponse (MessageContent) returns (MessageContent);
9+
rpc ReceiveStudentAgentResponse (ProcessDetails) returns (stream MessageContent);
10+
rpc PublishStudentAgentResponseFromProcess (MessageContent) returns (MessageContent);
11+
}
12+
13+
enum User {
14+
STUDENT = 0;
15+
TEACHER = 1;
16+
}
17+
18+
message MessageContent {
19+
User user = 1;
20+
string content = 2;
21+
string processId = 10;
22+
}
23+
24+
message ProcessDetails {
25+
string processId = 1;
26+
}

dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Grpc/Services/DocumentGenerationService.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using Microsoft.SemanticKernel;
77
using Microsoft.VisualStudio.Threading;
88
using ProcessWithCloudEvents.Grpc.Clients;
9-
using ProcessWithCloudEvents.Grpc.DocumentationGenerator;
9+
using ProcessWithCloudEvents.Grpc.Contract;
1010
using ProcessWithCloudEvents.Processes;
1111
using ProcessWithCloudEvents.Processes.Models;
1212

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Collections.Concurrent;
4+
using Dapr.Actors.Client;
5+
using Grpc.Core;
6+
using Microsoft.SemanticKernel;
7+
using Microsoft.VisualStudio.Threading;
8+
using ProcessWithCloudEvents.Grpc.Contract;
9+
using ProcessWithCloudEvents.Processes;
10+
11+
namespace ProcessWithCloudEvents.Grpc.Services;
12+
13+
/// <summary>
14+
/// This gRPC service handles the generation of documents using/invoking a SK Process
15+
/// </summary>
16+
public class TeacherStudentInteractionService : GrpcTeacherStudentInteraction.GrpcTeacherStudentInteractionBase
17+
{
18+
private readonly DaprKernelProcessFactory _kernelProcessFactory;
19+
private readonly ILogger<TeacherStudentInteractionService> _logger;
20+
private readonly Kernel _kernel;
21+
private readonly IActorProxyFactory _actorProxyFactory;
22+
private readonly ConcurrentDictionary<string, ConcurrentBag<IServerStreamWriter<MessageContent>>> _studentMessagesSubscribers;
23+
/// <summary>
24+
/// Constructor for the <see cref="DocumentGenerationService"/>
25+
/// </summary>
26+
/// <param name="logger"></param>
27+
/// <param name="kernel"></param>
28+
/// <param name="actorProxy"></param>
29+
/// <param name="kernelProcessFactory"></param>
30+
public TeacherStudentInteractionService(ILogger<TeacherStudentInteractionService> logger, Kernel kernel, IActorProxyFactory actorProxy, DaprKernelProcessFactory kernelProcessFactory)
31+
{
32+
this._logger = logger;
33+
this._kernel = kernel;
34+
this._actorProxyFactory = actorProxy;
35+
this._studentMessagesSubscribers = new();
36+
this._kernelProcessFactory = kernelProcessFactory;
37+
}
38+
39+
public override async Task<ProcessDetails> StartProcess(ProcessDetails request, ServerCallContext context)
40+
{
41+
var processId = string.IsNullOrEmpty(request.ProcessId) ? Guid.NewGuid().ToString() : request.ProcessId;
42+
var process = TeacherStudentProcess.CreateProcessBuilder().Build();
43+
44+
var processContext = await this._kernelProcessFactory.StartAsync(TeacherStudentProcess.Key, processId, new KernelProcessEvent()
45+
{
46+
Id = TeacherStudentProcess.ProcessEvents.StartProcess,
47+
Data = "Give me a welcome message with a brief summary of what you can do",
48+
},
49+
this._actorProxyFactory);
50+
51+
return new ProcessDetails { ProcessId = processId };
52+
}
53+
54+
public override async Task ReceiveStudentAgentResponse(ProcessDetails request, IServerStreamWriter<MessageContent> responseStream, ServerCallContext context)
55+
{
56+
var subscribers = this._studentMessagesSubscribers.GetOrAdd(request.ProcessId, []);
57+
subscribers.Add(responseStream);
58+
59+
try
60+
{
61+
// Wait until the client disconnects
62+
await context.CancellationToken.WaitHandle.ToTask();
63+
}
64+
finally
65+
{
66+
// Remove the subscriber when client disconnects
67+
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
68+
subscribers.TryTake(out responseStream);
69+
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
70+
}
71+
}
72+
73+
public override async Task<MessageContent> PublishStudentAgentResponseFromProcess(MessageContent request, ServerCallContext context)
74+
{
75+
if (this._studentMessagesSubscribers.TryGetValue(request.ProcessId, out var subscribers))
76+
{
77+
foreach (var subscriber in subscribers)
78+
{
79+
await subscriber.WriteAsync(request).ConfigureAwait(false);
80+
}
81+
}
82+
83+
return request;
84+
}
85+
86+
public override async Task<MessageContent> RequestStudentAgentResponse(MessageContent request, ServerCallContext context)
87+
{
88+
var process = TeacherStudentProcess.CreateProcessBuilder().Build();
89+
var processId = request.ProcessId;
90+
91+
var processContext = await this._kernelProcessFactory.StartAsync(DocumentGenerationProcess.Key, processId, new KernelProcessEvent()
92+
{
93+
Id = TeacherStudentProcess.ProcessEvents.TeacherAskedQuestion,
94+
Data = request.Content,
95+
},
96+
this._actorProxyFactory);
97+
98+
return request;
99+
}
100+
}

dotnet/samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Processes/ProcessWithCloudEvents.Processes.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13+
<ProjectReference Include="..\..\..\..\src\Agents\OpenAI\Agents.OpenAI.csproj" />
1314
<ProjectReference Include="..\..\..\..\src\Connectors\Connectors.OpenAI\Connectors.OpenAI.csproj" />
1415
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Abstractions\Process.Abstractions.csproj" />
1516
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Core\Process.Core.csproj" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel;
4+
using Microsoft.SemanticKernel.Agents.OpenAI;
5+
6+
namespace ProcessWithCloudEvents.Processes;
7+
8+
/// <summary>
9+
/// Components related to the SK Process for generating documentation
10+
/// </summary>
11+
public static class TeacherStudentProcess
12+
{
13+
/// <summary>
14+
/// The key that the process will be registered with in the SK process runtime.
15+
/// </summary>
16+
public static string Key => nameof(TeacherStudentProcess);
17+
18+
/// <summary>
19+
/// SK Process events emitted by <see cref="DocumentGenerationProcess"/>
20+
/// </summary>
21+
public static class ProcessEvents
22+
{
23+
/// <summary>
24+
/// Event to start the document generation process
25+
/// </summary>
26+
public const string StartProcess = nameof(StartProcess);
27+
/// <summary>
28+
/// Event emitted when the user rejects the document
29+
/// </summary>
30+
public const string TeacherAskedQuestion = nameof(TeacherAskedQuestion);
31+
}
32+
33+
/// <summary>
34+
/// SK Process topics emitted by <see cref="TeacherStudentProcess"/>
35+
/// Topics are used to emit events to external systems
36+
/// </summary>
37+
public static class InteractionTopics
38+
{
39+
public const string AgentResponseMessage = nameof(AgentResponseMessage);
40+
public const string AgentErrorMessage = nameof(AgentErrorMessage);
41+
}
42+
43+
/// <summary>
44+
/// Creates a process builder for the Document Generation SK Process
45+
/// </summary>
46+
/// <param name="processName">name of the SK Process</param>
47+
/// <returns>instance of <see cref="ProcessBuilder"/></returns>
48+
public static ProcessBuilder CreateProcessBuilder(string processName = "TeacherStudentProcess")
49+
{
50+
// Create the process builder
51+
ProcessBuilder processBuilder = new(processName);
52+
53+
// Add the steps
54+
var studentAgentStep = processBuilder.AddStepFromDeclarativeAgent(
55+
new()
56+
{
57+
Name = "Student",
58+
// On purpose not assigning AgentId, if not provided a new agent is created
59+
Description = "Solves problem given",
60+
Instructions = "Solve the problem given, if the question is repeated answer the question with a bit of humor emphasizing that the question was asked but still answering the question",
61+
Model = new()
62+
{
63+
Id = "gpt-4o",
64+
},
65+
Type = OpenAIAssistantAgentFactory.OpenAIAssistantAgentType,
66+
});
67+
68+
var proxyStep = processBuilder.AddProxyStep(id: "proxy", [InteractionTopics.AgentResponseMessage, InteractionTopics.AgentErrorMessage]);
69+
70+
// Orchestrate the external input events
71+
processBuilder
72+
.OnInputEvent(ProcessEvents.StartProcess)
73+
.SendEventTo(new(studentAgentStep));
74+
75+
processBuilder
76+
.OnInputEvent(ProcessEvents.TeacherAskedQuestion)
77+
.SendEventTo(new(studentAgentStep));
78+
79+
studentAgentStep
80+
.OnFunctionResult()
81+
.EmitExternalEvent(proxyStep, InteractionTopics.AgentResponseMessage);
82+
83+
studentAgentStep
84+
.OnFunctionError()
85+
.EmitExternalEvent(proxyStep, InteractionTopics.AgentErrorMessage)
86+
.StopProcess();
87+
88+
return processBuilder;
89+
}
90+
}

0 commit comments

Comments
 (0)