Skip to content
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

Python: .Net: Processes - Cloud Events supporting components for emitting events + MicrosoftGraph emit Demo #9712

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4bda496
Cloud events initial hook up changes + working sample
esttenorio Nov 14, 2024
2f60002
hooking up IServiceProvider to Subscribers class
esttenorio Nov 15, 2024
67f8114
demo samples showcasing cloud events with eventSubscribers
esttenorio Nov 15, 2024
df21978
removing unnecessary code from existing samples
esttenorio Nov 18, 2024
38f64a1
missing unnecessary code
esttenorio Nov 18, 2024
58b4b28
Merge branch 'main' into estenori/processes-cloud-events
esttenorio Nov 18, 2024
1e275a2
changing to support only one subscriber instance of creating an insta…
esttenorio Nov 18, 2024
8cbd24a
fixing spelling errors
esttenorio Nov 18, 2024
6685021
addressing pipeline failures
esttenorio Nov 18, 2024
1041662
adding warning exception
esttenorio Nov 18, 2024
6a77254
fixing formatting
esttenorio Nov 19, 2024
1c09f76
Updating account opening sample to introduce SK Event Subscribers + u…
esttenorio Nov 19, 2024
0db3b43
updating readme mermaid graphs to not cut off
esttenorio Nov 20, 2024
1719ca4
fixing readability of Step02c_AccountOpeningWithCloudEvents mermaid g…
esttenorio Nov 20, 2024
c4b232e
porting changes from other open PR
esttenorio Nov 28, 2024
7afa340
Adapting Step02 to use ProcessBuilder<>
esttenorio Nov 28, 2024
f279976
adjusting Demo sample with new OnInputEvent
esttenorio Nov 28, 2024
49d29bb
Adding Step03c with EventSubscribers
esttenorio Nov 28, 2024
1ba226d
Merge branch 'main' into estenori/processes-cloud-events
esttenorio Dec 4, 2024
63e096f
minor changes after merge
esttenorio Dec 4, 2024
e28b0d3
spelling errors fix
esttenorio Dec 4, 2024
7142123
spelling fix
esttenorio Dec 4, 2024
98c65a9
updating demo readme
esttenorio Dec 4, 2024
be5fcfe
Adding ProcessBuilder uts
esttenorio Dec 5, 2024
c3bd453
Adding comments to tests
esttenorio Dec 5, 2024
c09f234
working dapr simple publish implementation only
esttenorio Dec 11, 2024
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
1 change: 1 addition & 0 deletions dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<PackageVersion Include="FastBertTokenizer" Version="1.0.28" />
<PackageVersion Include="PdfPig" Version="0.1.9" />
<PackageVersion Include="Pinecone.NET" Version="2.1.1" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.9.0" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Memory.Data" Version="8.0.1" />
Expand Down
9 changes: 9 additions & 0 deletions dotnet/SK-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OllamaFunctionCalling", "sa
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAIRealtime", "samples\Demos\OpenAIRealtime\OpenAIRealtime.csproj", "{6154129E-7A35-44A5-998E-B7001B5EDE14}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessWithCloudEvents", "samples\Demos\ProcessWithCloudEvents\ProcessWithCloudEvents.csproj", "{36E94769-8A05-4009-808C-E23A0FD2A0F0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1141,6 +1143,12 @@ Global
{6154129E-7A35-44A5-998E-B7001B5EDE14}.Publish|Any CPU.Build.0 = Debug|Any CPU
{6154129E-7A35-44A5-998E-B7001B5EDE14}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6154129E-7A35-44A5-998E-B7001B5EDE14}.Release|Any CPU.Build.0 = Release|Any CPU
{36E94769-8A05-4009-808C-E23A0FD2A0F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{36E94769-8A05-4009-808C-E23A0FD2A0F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{36E94769-8A05-4009-808C-E23A0FD2A0F0}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
{36E94769-8A05-4009-808C-E23A0FD2A0F0}.Publish|Any CPU.Build.0 = Debug|Any CPU
{36E94769-8A05-4009-808C-E23A0FD2A0F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{36E94769-8A05-4009-808C-E23A0FD2A0F0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1297,6 +1305,7 @@ Global
{B35B1DEB-04DF-4141-9163-01031B22C5D1} = {0D8C6358-5DAA-4EA6-A924-C268A9A21BC9}
{481A680F-476A-4627-83DE-2F56C484525E} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
{6154129E-7A35-44A5-998E-B7001B5EDE14} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
{36E94769-8A05-4009-808C-E23A0FD2A0F0} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
Expand Down
58 changes: 58 additions & 0 deletions dotnet/samples/Demos/ProcessWithCloudEvents/AppConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft. All rights reserved.

internal sealed class AppConfig
{
/// <summary>
/// The configuration for the Azure EntraId authentication.
/// </summary>
public AzureEntraIdConfig? AzureEntraId { get; set; }

/// <summary>
/// Ensures that the configuration is valid.
/// </summary>
internal void Validate()
{
ArgumentNullException.ThrowIfNull(this.AzureEntraId?.ClientId, nameof(this.AzureEntraId.ClientId));
ArgumentNullException.ThrowIfNull(this.AzureEntraId?.TenantId, nameof(this.AzureEntraId.TenantId));

if (this.AzureEntraId.InteractiveBrowserAuthentication)
{
ArgumentNullException.ThrowIfNull(this.AzureEntraId.InteractiveBrowserRedirectUri, nameof(this.AzureEntraId.InteractiveBrowserRedirectUri));
}
else
{
ArgumentNullException.ThrowIfNull(this.AzureEntraId?.ClientSecret, nameof(this.AzureEntraId.ClientSecret));
}
}

internal sealed class AzureEntraIdConfig
{
/// <summary>
/// App Registration Client Id
/// </summary>
public string? ClientId { get; set; }

/// <summary>
/// App Registration Tenant Id
/// </summary>
public string? TenantId { get; set; }

/// <summary>
/// The client secret to use for the Azure EntraId authentication.
/// </summary>
/// <remarks>
/// This is required if InteractiveBrowserAuthentication is false. (App Authentication)
/// </remarks>
public string? ClientSecret { get; set; }

/// <summary>
/// Specifies whether to use interactive browser authentication (Delegated User Authentication) or App authentication.
/// </summary>
public bool InteractiveBrowserAuthentication { get; set; }

/// <summary>
/// When using interactive browser authentication, the redirect URI to use.
/// </summary>
public string? InteractiveBrowserRedirectUri { get; set; } = "http://localhost";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Process.Models;
using ProcessWithCloudEvents.Processes;
using ProcessWithCloudEvents.Processes.Steps;

namespace ProcessWithCloudEvents.Controllers;
public abstract class CounterBaseController : ControllerBase
{
internal Kernel Kernel { get; init; }
internal KernelProcess Process { get; init; }

private static readonly JsonSerializerOptions s_jsonOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
};

internal Kernel BuildKernel(GraphServiceClient? graphClient = null)
{
var builder = Kernel.CreateBuilder();
if (graphClient != null)
{
builder.Services.AddSingleton<GraphServiceClient>(graphClient);
}
return builder.Build();
}

internal KernelProcess InitializeProcess(ProcessBuilder process)
{
this.InitializeStateFile(process.Name);
var processState = this.LoadProcessState(process.Name);
return process.Build(processState);
}

private string GetTemporaryProcessFilePath(string processName)
{
return Path.Combine(Path.GetTempPath(), $"{processName}.json");
}

internal void InitializeStateFile(string processName)
{
// Initialize the path for the temporary file
var tempProcessFile = this.GetTemporaryProcessFilePath(processName);

// If the file does not exist, create it and initialize with zero
if (!System.IO.File.Exists(tempProcessFile))
{
System.IO.File.WriteAllText(tempProcessFile, "");
}
}

internal void SaveProcessState(string processName, KernelProcessStateMetadata processStateInfo)
{
var content = JsonSerializer.Serialize<KernelProcessStateMetadata>(processStateInfo, s_jsonOptions);
System.IO.File.WriteAllText(this.GetTemporaryProcessFilePath(processName), content);
}

internal KernelProcessStateMetadata? LoadProcessState(string processName)
{
try
{
using StreamReader reader = new(this.GetTemporaryProcessFilePath(processName));
var content = reader.ReadToEnd();
return JsonSerializer.Deserialize<KernelProcessStateMetadata>(content, s_jsonOptions);
}
catch (Exception)
{
return null;
}
}

internal void StoreProcessState(KernelProcess process)
{
var stateMetadata = process.ToProcessStateMetadata();
this.SaveProcessState(process.State.Name, stateMetadata);
}

internal KernelProcessStepState<CounterStepState>? GetCounterState(KernelProcess process)
{
// TODO: Replace when there is a better way of extracting snapshot of local state
return process.Steps
.First(step => step.State.Name == RequestCounterProcess.StepNames.Counter).State as KernelProcessStepState<CounterStepState>;
}

internal async Task<KernelProcess> StartProcessWithEventAsync(string eventName, object? eventData = null)
{
var runningProcess = await this.Process.StartAsync(this.Kernel, new() { Id = eventName, Data = eventData });
var processState = await runningProcess.GetStateAsync();
this.StoreProcessState(processState);

return processState;
}

public virtual async Task<int> IncreaseCounterAsync()
{
return await Task.FromResult(0);
}

public virtual async Task<int> DecreaseCounterAsync()
{
return await Task.FromResult(0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;
using ProcessWithCloudEvents.Processes;

namespace ProcessWithCloudEvents.Controllers;
[ApiController]
[Route("[controller]")]
public class CounterWithCloudStepsController : CounterBaseController
{
private readonly ILogger<CounterWithCloudStepsController> _logger;

public CounterWithCloudStepsController(ILogger<CounterWithCloudStepsController> logger, GraphServiceClient graphClient)
{
this._logger = logger;

this.Kernel = this.BuildKernel(graphClient);
this.Process = this.InitializeProcess(RequestCounterProcess.CreateProcessWithCloudSteps());
}

[HttpGet("increase", Name = "IncreaseWithCloudSteps")]
public override async Task<int> IncreaseCounterAsync()
{
var eventName = RequestCounterProcess.GetEventName(RequestCounterProcess.CounterProcessEvents.IncreaseCounterRequest);
var runningProcess = await this.StartProcessWithEventAsync(eventName);
var counterState = this.GetCounterState(runningProcess);

return counterState?.State?.Counter ?? -1;
}

[HttpGet("decrease", Name = "DecreaseWithCloudSteps")]
public override async Task<int> DecreaseCounterAsync()
{
var eventName = RequestCounterProcess.GetEventName(RequestCounterProcess.CounterProcessEvents.DecreaseCounterRequest);
var runningProcess = await this.StartProcessWithEventAsync(eventName);
var counterState = this.GetCounterState(runningProcess);

return counterState?.State?.Counter ?? -1;
}

[HttpGet("reset", Name = "ResetCounterWithCloudSteps")]
public async Task<int> ResetCounterAsync()
{
var eventName = RequestCounterProcess.GetEventName(RequestCounterProcess.CounterProcessEvents.ResetCounterRequest);
var runningProcess = await this.StartProcessWithEventAsync(eventName);
var counterState = this.GetCounterState(runningProcess);

return counterState?.State?.Counter ?? -1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;
using ProcessWithCloudEvents.Processes;

namespace ProcessWithCloudEvents.Controllers;
[ApiController]
[Route("[controller]")]
public class CounterWithCloudSubscribersController : CounterBaseController
{
private readonly ILogger<CounterWithCloudStepsController> _logger;

public CounterWithCloudSubscribersController(ILogger<CounterWithCloudStepsController> logger, GraphServiceClient graphClient)
{
this._logger = logger;
this.Kernel = this.BuildKernel();

var serviceProvider = new ServiceCollection()
.AddSingleton<GraphServiceClient>(graphClient)
.BuildServiceProvider();
this.Process = this.InitializeProcess(RequestCounterProcess.CreateProcessWithProcessSubscriber(serviceProvider));
}

[HttpGet("increase", Name = "IncreaseCounterWithCloudSubscribers")]
public override async Task<int> IncreaseCounterAsync()
{
var eventName = RequestCounterProcess.GetEventName(RequestCounterProcess.CounterProcessEvents.IncreaseCounterRequest);
var runningProcess = await this.StartProcessWithEventAsync(eventName);
var counterState = this.GetCounterState(runningProcess);

return counterState?.State?.Counter ?? -1;
}

[HttpGet("decrease", Name = "DecreaseCounterWithCloudSubscribers")]
public override async Task<int> DecreaseCounterAsync()
{
var eventName = RequestCounterProcess.GetEventName(RequestCounterProcess.CounterProcessEvents.DecreaseCounterRequest);
var runningProcess = await this.StartProcessWithEventAsync(eventName);
var counterState = this.GetCounterState(runningProcess);

return counterState?.State?.Counter ?? -1;
}

[HttpGet("reset", Name = "ResetCounterWithCloudSubscribers")]
public async Task<int> ResetCounterAsync()
{
var eventName = RequestCounterProcess.GetEventName(RequestCounterProcess.CounterProcessEvents.ResetCounterRequest);
var runningProcess = await this.StartProcessWithEventAsync(eventName);
var counterState = this.GetCounterState(runningProcess);

return counterState?.State?.Counter ?? -1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft. All rights reserved.

using Azure.Core;
using Azure.Identity;
using Microsoft.Graph;

public static class GraphServiceProvider
{
public static GraphServiceClient CreateGraphService()
{
string[] scopes;

var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) // Set the base path for appsettings.json
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) // Load appsettings.json
.AddUserSecrets<Program>()
.AddEnvironmentVariables()
.Build()
.Get<AppConfig>() ??
throw new InvalidOperationException("Configuration is not setup correctly.");

config.Validate();

TokenCredential credential = null!;
if (config.AzureEntraId!.InteractiveBrowserAuthentication) // Authentication As User
{
/// Use this if using user delegated permissions
scopes = ["User.Read", "Mail.Send"];

credential = new InteractiveBrowserCredential(
new InteractiveBrowserCredentialOptions
{
TenantId = config.AzureEntraId.TenantId,
ClientId = config.AzureEntraId.ClientId,
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri(config.AzureEntraId.InteractiveBrowserRedirectUri!)
});
}
else // Authentication As Application
{
scopes = ["https://graph.microsoft.com/.default"];

credential = new ClientSecretCredential(
config.AzureEntraId.TenantId,
config.AzureEntraId.ClientId,
config.AzureEntraId.ClientSecret);
}

return new GraphServiceClient(credential, scopes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Graph.Me.SendMail;
using Microsoft.Graph.Models;

namespace ProcessWithCloudEvents.MicrosoftGraph;

public static class GraphRequestFactory
{
public static SendMailPostRequestBody CreateEmailBody(string subject, string content, List<string> recipients)
{
var message = new SendMailPostRequestBody()
{
Message = new Microsoft.Graph.Models.Message()
{
Subject = subject,
Body = new()
{
ContentType = Microsoft.Graph.Models.BodyType.Text,
Content = content,
},
ToRecipients = recipients.Select(address => new Recipient { EmailAddress = new() { Address = address } }).ToList(),
},
SaveToSentItems = true,
};

return message;
}
}
Loading
Loading